/*
 * Copyright (C) 2008, 2009 Mihai Şucan
 *
 * This file is part of libmacrame.
 *
 * Libmacrame is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Libmacrame is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Libmacrame.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $URL: http://code.google.com/p/libmacrame $
 * $Date: 2009-04-21 13:37:06 +0300 $
 *
 */

/**
 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 * @version pre-alpha
 * @requires core.js
 * @requires core-js.js
 *
 * @fileOverview This is a plugin for libmacrame which adds important methods 
 * for manipulation the DOM.
 */


(function () {
// We will use $ to refer to libmacrame in this plugin.
var $ = libmacrame;

/*
 * If extend_objects is set to true:
 * the script will add during runtime new methods and properties to the DOM 
 * objects: Node, Element, Text, NodeList, HTMLCollection, NamedNodeMap, 
 * HTMLElement, HTMLLinkElement, HTMLAnchorElement, HTMLAreaElement, Event, 
 * EventTarget, KeyboardEvent.
 *
 * Note that some of the implementations of the new methods rely on the fact 
 * that this setting is set to true.
 */
var extend_objects = true;

// We store all the DOM-related methods in $.dom, for quick reuse in any case 
// where they are needed.

/**
 * Holds the DOM-related methods.
 *
 * <p>This plugin extends the global DOM objects, like Node, Element and 
 * NodeList.
 *
 * <p>The {@link $.dom.NodeList} methods are also used for extending the 
 * HTMLCollection object prototype, since the two interfaces serve similar 
 * purpose. However, due to <a 
 * href="https://bugzilla.mozilla.org/show_bug.cgi?id=300519">bug 300519</a>, in 
 * Gecko any changes to the HTMLCollection.prototype do not yield the expected 
 * results. As such, a work-around needs to be implemented. (TODO)
 *
 * <p>The new methods implement common functionality needed when working with 
 * the DOM.
 *
 * @namespace Holds the DOM-related methods.
 */
$.dom = {};

/**
 * Holds the DOM Node methods.
 *
 * <p>By default, the global <var>Node.prototype</var> object is extended to 
 * contain all the methods defined in this namespace.
 *
 * <p>All of these methods use their <var>this</var> object. As such, you need 
 * to pass the correct <var>this</var> object.
 *
 * <p>In the examples provided for each method we will assume that the objects 
 * defined are already extended. Thus code like 
 * <code><var>node</var>.prependChild(<var>child</var>)</code> can be written 
 * directly. Such code can be read as "add the node <var>child</var> as a new 
 * child to the <var>node</var> node, making sure it's the first child".
 *
 * @namespace Holds the DOM Node methods.
 *
 * @see $.dom
 * @see $.dom.Element
 * @see $.dom.NodeList
 */
$.dom.Node = {
  /**
   * Insert a new node as the first child to the current node.
   *
   * @example
   * <code>var <var>elem</var> = document.createElement('div');
   * // Insert the element as the first child to the document.body.
   * document.body.prependChild(<var>elem</var>);</code>
   *
   * @param {Node} child The node to prepend as a child.
   *
   * @throws {TypeError} If <var>child</var> is not an instance of Node.
   *
   * @returns {Node} The prepended child node.
   *
   * @see $.dom.NodeList.prependChild
   * @see $.dom.NodeList.appendChild
   */
  prependChild : function (child) {
    if (!(child instanceof Node)) {
      throw new TypeError('The first argument must be a DOM node.');
    }

    return this.insertBefore(child, this.firstChild);
  },

  /**
   * Remove the first child from the current node.
   *
   * @example
   * // Remove the first child from the document.body.
   * <code>document.body.removeFirstChild();</code>
   *
   * @returns {Node|null} The removed child node, or null if nothing was 
   * removed.
   *
   * @see $.dom.Node.removeLastChild
   * @see $.dom.NodeList.removeFirstChild
   * @see $.dom.NodeList.removeLastChild
   */
  removeFirstChild : function () {
    return this.firstChild ? this.removeChild(this.firstChild) : null;
  },

  /**
   * Remove the last child from the current node.
   *
   * @example
   * // Remove the last child from the document.body.
   * <code>document.body.removeLastChild();</code>
   *
   * @returns {Node|null} The removed child node, or null if nothing was 
   * removed.
   *
   * @see $.dom.Node.removeFirstChild
   * @see $.dom.NodeList.removeFirstChild
   * @see $.dom.NodeList.removeLastChild
   */
  removeLastChild : function () {
    return this.lastChild ? this.removeChild(this.lastChild) : null;
  },

  /**
   * Append the current node to the <var>target</var> node.
   *
   * <p>The current node is added to the list of children of the 
   * <var>target</var> node, becoming the last child.
   *
   * @example
   * <code>var <var>elem</var> = document.createElement('div');
   * // Add the element to the body.
   * <var>elem</var>.appendTo(document.body);</code>
   *
   * @param {Node} target The target node where to append the current node.
   *
   * @throws {TypeError} If <var>target</var> is not an instance of Node.
   *
   * @returns {Node} The node that was appended.
   *
   * @see $.dom.Node.prependTo
   * @see $.dom.NodeList.prependTo
   * @see $.dom.NodeList.appendTo
   */
  appendTo : function (target) {
    if (!(target instanceof Node)) {
      throw new TypeError('The first argument must be a DOM node.');
    }

    return target.appendChild(this);
  },

  /**
   * Prepend the current node to the <var>target</var> node.
   *
   * <p>The current node is added to the list of children of the 
   * <var>target</var> node, becoming the first child.
   *
   * @example
   * <code>var <var>elem</var> = document.createElement('div');
   * // Add the element to the body.
   * <var>elem</var>.prependTo(document.body);</code>
   *
   * @param {Node} target The target node where to prepend the current node.
   *
   * @throws {TypeError} If <var>target</var> is not an instance of Node.
   *
   * @returns {Node} The node that was prepended.
   *
   * @see $.dom.Node.appendTo
   * @see $.dom.NodeList.prependTo
   * @see $.dom.NodeList.appendTo
   */
  prependTo : function (target) {
    if (!(target instanceof Node)) {
      throw new TypeError('The first argument must be a DOM node.');
    }

    return target.insertBefore(this, target.firstChild);
  },

  /**
   * Insert the specified node after a reference node to the current node.
   * 
   * <p>The <var>new_node</var> will be added as a child of the current node, 
   * after the <var>ref_node</var>.
   *
   * <p>This method works like the native <code>node.insertBefore()</code> 
   * method, which is actually used in the implementation.
   *
   * @example
   * <code>var <var>elem</var> = document.createElement('div'),
   *     <var>ref</var> = $('#foo');
   * document.body.insertAfter(<var>elem</var>, <var>ref</var>);</code>
   *
   * @param {Node} new_node The new node to insert as a child.
   * @param {Node} ref_node The reference child node after which you want to 
   * insert the given new child node. If this argument is null, the new node is 
   * simply appended as a child, becoming the last in the list of children.
   *
   * @throws {TypeError} If <var>new_node</var> is not an instance of Node.
   *
   * @returns {Node} The node that was inserted.
   *
   * @see $.dom.NodeList.insertAfter
   * @see $.dom.NodeList.insertBefore
   */
  insertAfter : function (new_node, ref_node) {
    if (!(new_node instanceof Node)) {
      throw new TypeError('The first argument must be a DOM node.');
    }

    var before = ref_node ? ref_node.nextSibling : null;
    return this.insertBefore(new_node, before);
  }
};

/**
 * Holds the DOM Element methods.
 *
 * <p>By default, the global <var>Element.prototype</var> object is extended to 
 * contain all the methods defined in this namespace.
 *
 * <p>The global <var>Text.prototype</var> and <var>Comment.prototype</var> DOM 
 * objects are also extended. However, these two prototypes are only extended to 
 * contain the new {@link $.dom.Element.wrap} method.
 *
 * <p>All of these methods use their <var>this</var> object. As such, you need 
 * to pass the correct <var>this</var> object.
 *
 * <p>In the examples provided for each method we will assume that the objects 
 * defined are already extended. Thus code like 
 * <code><var>element</var>.wrap('&lt;p&gt;')</code> can be written directly.
 *
 * @namespace Holds the DOM Element methods.
 *
 * @see $.dom.Node
 * @see $.dom.NodeList
 */
$.dom.Element = {
  /**
   * Wrap the element inside a given wrapper.
   *
   * @example
   * <code>var <var>elem</var> = document.createElement('p');
   *
   * document.body.appendChild(<var>elem</var>);</code>
   *
   * // Now the body would serialize to something like:
   * // &lt;body&gt;&lt;p&gt;&lt;/p&gt;&lt;/body&gt;
   *
   * <code>var <var>result</var> = <var>elem</var>.wrap('&lt;div&gt;');</code>
   *
   * // Now <var>result</var> = the &lt;div&gt; node.
   * // The body would serialize to something like:
   * // &lt;body&gt;&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;/body&gt;
   *
   * @example
   * <code>var <var>elem</var> = document.createElement('p');
   *
   * document.body.appendChild(<var>elem</var>);</code>
   *
   * // Now the body would serialize to something like:
   * // &lt;body&gt;&lt;p&gt;&lt;/p&gt;&lt;/body&gt;
   *
   * <code>var <var>wrapper</var> = '&lt;div id=&#34;test1&#34;&gt;' +
   *               '&lt;div id=&#34;test2&#34;&gt;';
   *
   * var <var>result</var> = <var>elem</var>.wrap(<var>wrapper</var>);</code>
   *
   * // Now <var>result</var> = the #test1 node.
   * // The body would serialize to something like:
   * // &lt;body&gt;&lt;div id=&#34;test1&#34;&gt;&lt;div id=&#34;test2&#34;&gt;&lt;p&gt;
   * // &lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/body&gt;
   *
   * @example
   * <code>var <var>elem</var> = document.createElement('p');
   *
   * document.body.appendChild(<var>elem</var>);</code>
   *
   * // Now the body would serialize to something like:
   * // &lt;body&gt;&lt;p&gt;&lt;/p&gt;&lt;/body&gt;
   *
   * <code>var <var>wrapper1</var> = document.createElement('div'),
   *     <var>wrapper2</var> = document.createElement('div');
   *
   * <var>wrapper1</var>.id = 'test1';
   * <var>wrapper1</var>.appendChild(<var>wrapper2</var>);
   * <var>wrapper2</var>.id = 'test2';
   *
   * var <var>result</var> = <var>elem</var>.wrap(<var>wrapper1</var>);</code>
   *
   * // Now <code><var>result</var> = <var>wrapper1</var></code>.
   * // The body would serialize to something like:
   * // &lt;body&gt;&lt;div id=&#34;test1&#34;&gt;&lt;div id=&#34;test2&#34;&gt;&lt;p&gt;
   * // &lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/body&gt;
   *
   * @param {Element|String} wrapper The element or the string to use for 
   * wrapping the current element.
   *
   * <p>If the wrapper is an element, then it is be used as-is for wrapping.
   *
   * <p>If the wrapper is a string, it is be parsed as HTML code. Then, the 
   * firstElementChild of the DOM tree generated by the string becomes the 
   * wrapper.
   *
   * <p>For both cases the determined wrapper gets added to the parent node of 
   * the current element (the <var>this</var> object), if it's available.
   *
   * <p>The deepest child element of the wrapper is searched, so that the 
   * current element will be appended into it.
   *
   * @throws {TypeError} If the current node is not an element/text/comment 
   * node. This method cannot wrap other kinds of objects.
   *
   * @throws {ReferenceError} If the wrapper is a string and the current element 
   * has no owner document.
   *
   * @throws {ReferenceError} If the wrapper is a string with no elements in it.
   *
   * @returns {Element} The wrapper element.
   *
   * @see $.dom.Element.wrapInner
   */
  wrap : function (wrapper) {
    if (this.nodeType != Node.ELEMENT_NODE && this.nodeType != 
        Node.TEXT_NODE && this.nodeType != Node.COMMENT_NODE) {
      throw new TypeError('This node must be an DOM element/text/comment node.');
    }

    var elem;

    if (typeof wrapper == 'string') {
      var doc = this.ownerDocument;
      if (doc.nodeType != Node.DOCUMENT_NODE) {
        throw new ReferenceError('This node does not have an owner document. You cannot wrap it into a string.');
      }

      var div = doc.createElement('div');
      div.innerHTML = wrapper;
      elem = div.firstElementChild;
      if (!elem) {
        throw new ReferenceError('The provided string does not contain any element tag.');
      }

    } else if (wrapper.nodeType == Node.ELEMENT_NODE) {
      elem = wrapper;
    } else {
      throw new TypeError('The first argument must be a DOM element or a string.');
    }

    if (this.parentNode) {
      this.parentNode.insertBefore(elem, this);
    }

    // Find the deepest child element.
    var first = elem;
    while (first.firstElementChild) {
      first = first.firstElementChild;
    }

    first.appendChild(this);

    return elem;
  },

  /**
   * Wrap all the child nodes of the current element.
   *
   * <p>The <var>wrapper</var> argument is the same as for {@link 
   * $.dom.Element.wrap}.
   *
   * @example
   * <code>var <var>child1</var> = document.createElement('p'),
   *     <var>child2</var> = document.createElement('p');
   *
   * <var>child1</var>.id = 'child1';
   * <var>child2</var>.id = 'child2';
   *
   * document.body.appendChild(<var>child1</var>);
   * document.body.appendChild(<var>child2</var>);</code>
   * // Now the body contains the two children.
   *
   * <code>var result = document.body.wrapInner('&lt;div&gt;');</code>
   * // Now <var>result</var> is a NodeList containing <var>child1</var>
   * // and <var>child2</var>.
   *
   * // The body would serialize to something like:
   * // &lt;body&gt;&lt;div&gt;&lt;p id=&#34;child1&#34;&gt;&lt;/p&gt;
   * // &lt;p id=&#34;chil2&#34;&gt;&lt;/p&gt;&lt;/div&gt;&lt;/body&gt;
   * 
   * @param {Element|String} wrapper The element or the string to use for 
   * wrapping the child nodes of the current element.
   *
   * @throws {TypeError} If the current node is not an element node. This method 
   * cannot wrap other kinds of objects.
   *
   * @throws {ReferenceError} If the wrapper is a string and the current element 
   * has no owner document.
   *
   * @throws {ReferenceError} If the wrapper is a string with no elements in it.
   *
   * @returns {NodeList} The list of wrapped child nodes.
   * (<code><var>wrapper</var>.childNodes</code>)
   *
   * @see $.dom.Element.wrap
   */
  wrapInner : function (wrapper) {
    if (this.nodeType != Node.ELEMENT_NODE) {
      throw new TypeError('This node must be an DOM element.');
    }

    if (this.hasChildNodes()) {
      return $.dom.NodeList.wrapAll.call(this.childNodes, wrapper);
    } else {
      return null;
    }
  }
};

// Make sure we can wrap text and comment nodes as well.
$.dom.Text = $.dom.Comment = {
  wrap : $.dom.Element.wrap
};

/**
 * Holds the DOM NodeList methods, which allow the manipulation of multiple 
 * nodes in a single step.
 *
 * <p>By default, the global <var>NodeList.prototype</var> object is extended to 
 * contain all the methods defined in this namespace.
 *
 * <p>These methods are also used for extending the global 
 * <var>HTMLCollection.prototype</var> object.
 *
 * <p>All of these methods use their <var>this</var> object. As such, you need 
 * to pass the correct <var>this</var> object.
 *
 * <p>In the examples provided for each method we will assume that the objects 
 * defined are already extended. Thus code like 
 * <code><var>element.childNodes</var>.wrap('&lt;p&gt;')</code> can be written 
 * directly.
 *
 * @example
 * // DOM elements have the <var>.childNodes</var> property object which holds
 * // the list of child nodes. This object is an instance of NodeList. By
 * // extending the <var>NodeList.prototype</var> you can do the following:
 *
 * var <var>new_child</var> = document.createElement('p');
 * document.body.childNodes.appendChild(<var>new_child</var>);
 *
 * // The above code appends a clone of the <var>new_child</var> into each child
 * // node of the document body.
 *
 * // As expected, you can use any of the methods defined in this namespace.
 *
 * @namespace Holds the DOM NodeList methods, which allow the manipulation of 
 * multiple nodes in a single step.
 *
 * @requires $.js.Array
 *
 * @see $.dom.Node
 * @see $.dom.Element
 */
$.dom.NodeList = {
  /**
   * Convert the current NodeList to an array.
   *
   * <p>Each NodeList item becomes an array element. The array has the same 
   * length as the NodeList object.
   *
   * <p>The returned array is a static list. If the NodeList changes, the array 
   * does not.
   *
   * @returns {Array} The current NodeList converted to an array, having the 
   * same length.
   *
   * @see $.js.Array
   */
  toArray : function () {
    var n = this.length;
    var resArr = new Array(n);

    for (var i = 0; i < n; i++) {
      resArr[i] = this[i];
    }

    return resArr;
  },

  // Array methods

  /**
   * Extract a section of the current NodeList and return it.
   *
   * @function
   *
   * @param {Number} begin
   *
   * @param {Number} [end=this.length]
   *
   * @throws {TypeError} If <var>begin</var> is not a number, or if it is 
   * a negative number.
   *
   * @throws {TypeError} If <var>end</var> is provided, but it is not a number.
   *
   * @returns {Array} The new array returned contains all the nodes starting 
   * from the <var>begin</var> index (including it) up to the <var>end</var> 
   * index (excluding it).
   *
   * @requires $.js.Array.slice
   * @see $.js.Array.slice for more details and examples
   */
  slice : $.js.Array.slice,

  /**
   * Filter the current NodeList using the <var>callback</var> function.
   *
   * @function
   *
   * @param {Function} callback
   *
   * @param {Object} [thisObject]
   *
   * @throws {TypeError} If <var>callback</var> is not a function.
   *
   * @returns {Array} The new array contains the nodes for which the 
   * <var>callback</var> function returned true.
   *
   * @requires $.js.Array.filter
   * @see $.js.Array.filter for more details and examples
   */
  filter : $.js.Array.filter,

  /**
   * Execute the <var>callback</var> function for each node in the current 
   * NodeList.
   *
   * @function
   *
   * @param {Function} callback
   *
   * @param {Object} [thisObject]
   *
   * @throws {TypeError} If <var>callback</var> is not a function.
   *
   * @requires $.js.Array.forEach
   * @see $.js.Array.forEach for more details and examples
   */
  forEach : $.js.Array.forEach,

  /**
   * Check if the <var>callback</var> function returns true for every node in 
   * the current NodeList.
   *
   * @function
   *
   * @param {Function} callback
   *
   * @param {Object} [thisObject]
   *
   * @throws {TypeError} If <var>callback</var> is not a function.
   *
   * @returns {Boolean} False is returned if the <var>callback</var> returns 
   * false once.  Otherwise, this method returns true.
   *
   * @requires $.js.Array.every
   * @see $.js.Array.every for more details and examples
   */
  every : $.js.Array.every,

  /**
   * Check if the <var>callback</var> function returns true for at least one 
   * node in the current NodeList.
   *
   * @function
   *
   * @param {Function} callback
   *
   * @param {Object} [thisObject]
   *
   * @throws {TypeError} If <var>callback</var> is not a function.
   *
   * @returns {Boolean} True is returned if the <var>callback</var> returns true 
   * once. Otherwise, this method returns false.
   *
   * @requires $.js.Array.some
   * @see $.js.Array.some for more details and examples
   */
  some : $.js.Array.some,

  /**
   * Create a new array with the same length as the current NodeList using the 
   * values returned by the <var>callback</var> function.
   *
   * @function
   *
   * @param {Function} callback
   *
   * @param {Object} [thisObject]
   *
   * @throws {TypeError} If <var>callback</var> is not a function.
   *
   * @returns {Boolean} The new array has the same length, but the values are 
   * those returned by the <var>callback</var> function.
   *
   * @requires $.js.Array.map
   * @see $.js.Array.map for more details and examples
   */
  map : $.js.Array.map,

  /**
   * Apply the <var>callback</var> function to two values in the current 
   * NodeList, from left to right, simultaneously, for the purpose of reducing 
   * the NodeList to a single value.
   *
   * @function
   *
   * @param {Function} callback
   *
   * @param [initialValue]
   *
   * @throws {TypeError} If <var>callback</var> is not a function.
   *
   * @returns The result of the last <var>callback</var> function invocation.
   *
   * @requires $.js.Array.reduce
   * @see $.js.Array.reduce for more details and examples
   * @see $.dom.NodeList.reduceRight
   */
  reduce : $.js.Array.reduce,

  /**
   * Apply the <var>callback</var> function to two values from the current 
   * NodeList, from right to left, simultaneously, for the purpose of reducing 
   * the NodeList to a single value.
   *
   * @function
   *
   * @param {Function} callback
   *
   * @param [initialValue]
   *
   * @throws {TypeError} If <var>callback</var> is not a function.
   *
   * @returns The result of the last <var>callback</var> function invocation.
   *
   * @requires $.js.Array.reduceRight
   * @see $.js.Array.reduceRight for more details and examples
   * @see $.dom.NodeList.reduce
   */
  reduceRight : $.js.Array.reduceRight,

  /**
   * Create a new array containing the list of child elements of each item in 
   * the current NodeList.
   *
   * @example
   * // Given a DOM tree like:
   * <code>&lt;body&gt;
   *   &lt;ul&gt;
   *     &lt;li&gt;test 1.1&lt;/li&gt;
   *     &lt;li&gt;test 1.2&lt;/li&gt;
   *   &lt;/ul&gt;
   *   &lt;ol&gt;
   *     &lt;li&gt;test 2.1&lt;/li&gt;
   *     &lt;li&gt;test 2.2&lt;/li&gt;
   *   &lt;/ol&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do:
   * <code>var <var>elems</var> = document.body.childNodes.children();</code>
   *
   * // Now <var>elems</var> is an array having four elements: the &lt;li&gt;
   * // nodes from the two body child nodes, the &lt;ul&gt; and the &lt;ol&gt;.
   *
   * @returns {Array} The array containing the child elements of each item in 
   * the current NodeList.
   */
  children : function () {
    var resArr = [];

    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE && elem.children) {
        $.js.Array.forEach.call(elem.children, function (child) {
          resArr.push(child);
        });
      }
    });

    return resArr;
  },

  /**
   * Add the <var>token</var> to the class list of each element in the current 
   * NodeList.
   *
   * <p>This method takes a single token, no spaces allowed.
   *
   * @example
   * <code>document.body.childNodes.addClass('test');</code>
   *
   * @param {String} token The token to add to the list of class names, for each 
   * element in the current NodeList.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
   * contains a space character.
   *
   * @see $.dom.NodeList.hasClass
   * @see $.dom.NodeList.toggleClass
   * @see $.dom.NodeList.removeClass
   */
  addClass : function (token) {
    if (!token) {
      return;
    }

    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE && elem.classList) {
        elem.classList.add(token);
      }
    });
  },

  /**
   * Remove the <var>token</var> from the class list of each element in the 
   * current NodeList.
   *
   * <p>This method takes a single token, no spaces allowed.
   *
   * @example
   * <code>document.body.childNodes.removeClass('test');</code>
   *
   * @param {String} token The token you want removed from the list of class 
   * names, for each element in the current NodeList.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
   * contains a space character.
   *
   * @see $.dom.NodeList.addClass
   * @see $.dom.NodeList.hasClass
   * @see $.dom.NodeList.toggleClass
   */
  removeClass : function (token) {
    if (!token) {
      return;
    }

    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE && elem.className && 
        elem.classList) {
        elem.classList.remove(token);
      }
    });
  },

  /**
   * Toggle the presence of <var>token</var> in the class list of each element 
   * in the current NodeList.
   *
   * <p>This method takes a single token, no spaces allowed.
   *
   * <p>For each element, it is checked if the <var>token</var> is found in the 
   * list of class names. If yes, then the <var>token</var> is removed. If not, 
   * then the <var>token</var> is added.
   *
   * @example
   * <code>document.body.childNodes.toggleClass('test');</code>
   *
   * @param {String} token The token you want to toggle in the list of class 
   * names, for each element in the current NodeList.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
   * contains a space character.
   *
   * @see $.dom.NodeList.addClass
   * @see $.dom.NodeList.hasClass
   * @see $.dom.NodeList.removeClass
   */
  toggleClass : function (token) {
    if (!token) {
      return;
    }

    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE && elem.classList) {
        elem.classList.toggle(token);
      }
    });
  },

  /**
   * Check if all the elements in the current NodeList have the <var>token</var> 
   * in their list of class names.
   *
   * <p>This method takes a single token, no spaces allowed.
   *
   * @example
   * <code>document.body.childNodes.hasClass('test');</code>
   *
   * @param {String} token The token you want to toggle in the list of class 
   * names, for each element in the current NodeList.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
   * contains a space character.
   *
   * @returns {Boolean} This method returns true if all the elements have the 
   * <var>token</var> in their class list. Otherwise, false is returned.
   *
   * @see $.dom.NodeList.addClass
   * @see $.dom.NodeList.toggleClass
   * @see $.dom.NodeList.removeClass
   */
  hasClass : function (token) {
    if (!token) {
      return false;
    }

    var found_elem = false;
    var res = this.every(function (elem) {
      if (elem.nodeType != Node.ELEMENT_NODE) {
        return true;
      }

      found_elem = true;

      if (elem.className && elem.classList) {
        return elem.classList.has(token);
      } else {
        return false;
      }
    });

    if (!found_elem) {
      res = false;
    }

    return res;
  },

  /**
   * Set the value of an attribute, for each element in the current NodeList.
   *
   * @example
   * // Set the border attribute for all images in the document.
   * <code>var imgs = $('img');
   * imgs.setAttribute('border', 0);</code>
   *
   * @param {String} attr The attribute name.
   * @param {String} val The attribute value you want to set.
   *
   * @see $.dom.NodeList.setAttributeNS
   * @see $.dom.NodeList.removeAttribute
   * @see $.dom.NodeList.removeAttributeNS
   * @see $.dom.NodeList.hasAttribute
   * @see $.dom.NodeList.hasAttributeNS
   */
  setAttribute : function (attr, val) {
    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        elem.setAttribute(attr, val);
      }
    });
  },

  /**
   * Set the value of a namespaced attribute, for each element in the current 
   * NodeList.
   *
   * @example
   * // Set the border attribute for all images in the document.
   * <code>var imgs = $('img'),
   *     ns = '<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>';
   * imgs.setAttributeNS(ns, 'border', 0);</code>
   *
   * @param {String} ns The attribute XML namespace.
   * @param {String} attr The attribute name.
   * @param {String} val The attribute value you want to set.
   *
   * @see $.dom.NodeList.setAttribute
   * @see $.dom.NodeList.removeAttribute
   * @see $.dom.NodeList.removeAttributeNS
   * @see $.dom.NodeList.hasAttribute
   * @see $.dom.NodeList.hasAttributeNS
   */
  setAttributeNS : function (ns, attr, val) {
    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        elem.setAttributeNS(ns, attr, val);
      }
    });
  },

  /**
   * Remove an attribute from all the elements in the current NodeList.
   *
   * @example
   * // Remove the border attribute from all the images in the document.
   * <code>var imgs = $('img');
   * imgs.removeAttribute('border');</code>
   *
   * @param {String} attr The attribute name to be removed.
   *
   * @see $.dom.NodeList.setAttribute
   * @see $.dom.NodeList.setAttributeNS
   * @see $.dom.NodeList.removeAttributeNS
   * @see $.dom.NodeList.hasAttribute
   * @see $.dom.NodeList.hasAttributeNS
   */
  removeAttribute : function (attr) {
    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        elem.removeAttribute(attr);
      }
    });
  },

  /**
   * Remove a namespaced attribute from all the elements in the current 
   * NodeList.
   *
   * @example
   * // Remove the border attribute from all the images in the document.
   * <code>var imgs = $('img'),
   *     ns = '<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>';
   * imgs.removeAttributeNS(ns, 'border');</code>
   *
   * @param {String} ns The attribute XML namespace.
   * @param {String} attr The attribute name to be removed.
   *
   * @see $.dom.NodeList.setAttribute
   * @see $.dom.NodeList.setAttributeNS
   * @see $.dom.NodeList.removeAttribute
   * @see $.dom.NodeList.hasAttribute
   * @see $.dom.NodeList.hasAttributeNS
   */
  removeAttributeNS : function (ns, attr) {
    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        elem.removeAttributeNS(ns, attr);
      }
    });
  },

  /**
   * Check if an attribute is defined for all the elements in the current 
   * NodeList.
   *
   * @example
   * // Check if the border attribute is set for all the image elements.
   * <code>var imgs = $('img');
   * imgs.hasAttribute('border');</code>
   *
   * @param {String} attr The attribute name to be checked.
   *
   * @see $.dom.NodeList.setAttribute
   * @see $.dom.NodeList.setAttributeNS
   * @see $.dom.NodeList.removeAttribute
   * @see $.dom.NodeList.removeAttributeNS
   * @see $.dom.NodeList.hasAttributeNS
   */
  hasAttribute : function (attr) {
    var found_elem = false;

    var res = this.every(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        found_elem = true;
        return elem.hasAttribute(attr);
      } else {
        return true;
      }
    });

    if (!found_elem) {
      res = false;
    }

    return res;
  },

  /**
   * Check if a namespaced attribute is defined for all the elements in the 
   * current NodeList.
   *
   * @example
   * // Check if the border attribute is set for all the image elements.
   * <code>var imgs = $('img'),
   *     ns = '<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>';
   * imgs.hasAttributeNS(ns, 'border');</code>
   *
   * @param {String} ns The attribute XML namespace.
   * @param {String} attr The attribute name to be checked.
   *
   * @see $.dom.NodeList.setAttribute
   * @see $.dom.NodeList.setAttributeNS
   * @see $.dom.NodeList.removeAttribute
   * @see $.dom.NodeList.removeAttributeNS
   * @see $.dom.NodeList.hasAttribute
   */
  hasAttributeNS : function (ns, attr) {
    var found_elem = false;

    var res = this.every(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        found_elem = true;
        return elem.hasAttributeNS(ns, attr);
      } else {
        return true;
      }
    });

    if (!found_elem) {
      res = false;
    }

    return res;
  },

  /**
   * Insert a new node as the first child into each element from the current 
   * NodeList.
   *
   * <p>Given a node, this method clones it for every element in the current 
   * NodeList. The clone is then inserted as the first child into each element.
   *
   * @example
   * // Given a DOM tree like:
   * <code>&lt;body&gt;
   *   &lt;ul&gt;
   *     &lt;li&gt;test 1.1&lt;/li&gt;
   *     &lt;li&gt;test 1.2&lt;/li&gt;
   *   &lt;/ul&gt;
   *   &lt;ol&gt;
   *     &lt;li&gt;test 2.1&lt;/li&gt;
   *     &lt;li&gt;test 2.2&lt;/li&gt;
   *   &lt;/ol&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do:
   * <code>var <var>elem</var> = document.createElement('li');
   * <var>elem</var>.className = 'test';</code>
   *
   * // Prepend the element to the document.body child nodes.
   * <code>document.body.childNodes.prependChild(<var>elem</var>);</code>
   *
   * // The body would then serialize to something like:
   * <code>&lt;body&gt;
   *   &lt;ul&gt;
   *     &lt;li class="test"&gt;&lt;/li&gt;
   *     &lt;li&gt;test 1.1&lt;/li&gt;
   *     &lt;li&gt;test 1.2&lt;/li&gt;
   *   &lt;/ul&gt;
   *   &lt;ol&gt;
   *     &lt;li class="test"&gt;&lt;/li&gt;
   *     &lt;li&gt;test 2.1&lt;/li&gt;
   *     &lt;li&gt;test 2.2&lt;/li&gt;
   *   &lt;/ol&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Node} child The node to prepend as a child.
   *
   * @returns {Array} An array containing the inserted child nodes.
   *
   * @see $.dom.NodeList.appendChild
   * @see $.dom.Node.prependChild
   */
  prependChild : function (child) {
    var resArr = [];

    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        var clone = child.cloneNode(true);
        resArr.push(elem.insertBefore(clone, elem.firstChild));
      }
    });

    return resArr;
  },

  /**
   * Insert a new node as the last child into each element from the current 
   * NodeList.
   *
   * <p>Given a node, this method clones it for every element in the current 
   * NodeList. The clone is then appended as the last child into each element.
   *
   * @example
   * // Given a DOM tree like:
   * <code>&lt;body&gt;
   *   &lt;ul&gt;
   *     &lt;li&gt;test 1.1&lt;/li&gt;
   *     &lt;li&gt;test 1.2&lt;/li&gt;
   *   &lt;/ul&gt;
   *   &lt;ol&gt;
   *     &lt;li&gt;test 2.1&lt;/li&gt;
   *     &lt;li&gt;test 2.2&lt;/li&gt;
   *   &lt;/ol&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do:
   * <code>var <var>elem</var> = document.createElement('li');
   * <var>elem</var>.className = 'test';</code>
   *
   * // Append the element to the document.body child nodes.
   * <code>document.body.childNodes.appendChild(<var>elem</var>);</code>
   *
   * // The body would then serialize to something like:
   * <code>&lt;body&gt;
   *   &lt;ul&gt;
   *     &lt;li&gt;test 1.1&lt;/li&gt;
   *     &lt;li&gt;test 1.2&lt;/li&gt;
   *     &lt;li class="test"&gt;&lt;/li&gt;
   *   &lt;/ul&gt;
   *   &lt;ol&gt;
   *     &lt;li&gt;test 2.1&lt;/li&gt;
   *     &lt;li&gt;test 2.2&lt;/li&gt;
   *     &lt;li class="test"&gt;&lt;/li&gt;
   *   &lt;/ol&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Node} child The node to append as a child.
   *
   * @returns {Array} An array containing the inserted child nodes.
   *
   * @see $.dom.NodeList.prependChild
   * @see $.dom.Node.prependChild
   */
  appendChild : function (child) {
    var resArr = [];

    this.forEach(function (elem) {
      if (elem.nodeType == Node.ELEMENT_NODE) {
        var clone = child.cloneNode(true);
        resArr.push(elem.appendChild(clone));
      }
    });

    return resArr;
  },

  /**
   * Prepend all the nodes in the current NodeList to the <var>target</var> 
   * node. All the nodes are inserted maintaining the same order as in the 
   * current NodeList.
   *
   * @example
   * // Given a DOM tree like:
   * <code>&lt;body&gt;
   *   &lt;div&gt;
   *     &lt;p&gt;test a&lt;/p&gt;
   *     &lt;p&gt;test b&lt;/p&gt;
   *   &lt;/div&gt;
   *   &lt;p&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do the following:
   * <code>var <var>div</var> = document.body.firstElementChild;</code>
   *
   * // Move the &lt;div&gt; child nodes into the body element.
   * <code><var>div</var>.childNodes.prependTo(document.body);</code>
   *
   * // Now the body would serialize to something like:
   * <code>&lt;body&gt;
   *   &lt;p&gt;test a&lt;/p&gt;
   *   &lt;p&gt;test b&lt;/p&gt;
   *   &lt;div&gt;&lt;/div&gt;
   *   &lt;p&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Node} target The target node where to prepend all of the nodes from 
   * the current NodeList.
   *
   * @throws {TypeError} If <var>target</var> is not an instance of Node.
   *
   * @returns {NodeList} The child nodes of the <var>target</var> node.
   * (<code><var>target</var>.childNodes</code>)
   *
   * @see $.dom.NodeList.appendTo
   * @see $.dom.Node.prependTo
   * @see $.dom.Node.appendTo
   */
  prependTo : function (target) {
    if (!this.length) {
      return;
    }

    if (!(target instanceof Node)) {
      throw new TypeError('The first argument must be a DOM node.');
    }

    var first = target.firstChild;

    while(this[0]) {
      target.insertBefore(this[0], first);
    }

    return target.childNodes;
  },

  /**
   * Append all the nodes in the current NodeList to the <var>target</var> node.  
   * All the nodes are inserted maintaining the same order as in the current 
   * NodeList.
   *
   * @example
   * // Given a DOM tree like:
   * <code>&lt;body&gt;
   *   &lt;div&gt;
   *     &lt;p&gt;test a&lt;/p&gt;
   *     &lt;p&gt;test b&lt;/p&gt;
   *   &lt;/div&gt;
   *   &lt;p&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do the following:
   * <code>var <var>div</var> = document.body.firstElementChild;</code>
   *
   * // Move the &lt;div&gt; child nodes into the body element.
   * <code><var>div</var>.childNodes.appendTo(document.body);</code>
   *
   * // Now the body would serialize to something like:
   * <code>&lt;body&gt;
   *   &lt;div&gt;&lt;/div&gt;
   *   &lt;p&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   *   &lt;p&gt;test a&lt;/p&gt;
   *   &lt;p&gt;test b&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Node} target The target node where to append all of the nodes from 
   * the current NodeList.
   *
   * @throws {TypeError} If <var>target</var> is not an instance of Node.
   *
   * @returns {NodeList} The child nodes of the <var>target</var> node.
   * (<code><var>target</var>.childNodes</code>)
   *
   * @see $.dom.NodeList.prependTo
   * @see $.dom.Node.prependTo
   * @see $.dom.Node.appendTo
   */
  appendTo : function (target) {
    if (!this.length) {
      return;
    }

    if (!(target instanceof Node)) {
      throw new TypeError('The first argument must be a DOM node.');
    }

    while(this[0]) {
      target.appendChild(this[0]);
    }

    return target.childNodes;
  },

  /**
   * Insert all the nodes in the current NodeList before a reference element.
   *
   * <p>The reference element must have a parent element. All the nodes from the 
   * current NodeList will be inserted into the parent element. The inserted 
   * nodes will be positioned before the reference element.
   *
   * @example
   * // Given a DOM tree like:
   * <code>&lt;body&gt;
   *   &lt;div&gt;
   *     &lt;p&gt;test a&lt;/p&gt;
   *     &lt;p&gt;test b&lt;/p&gt;
   *   &lt;/div&gt;
   *   &lt;p id="test1"&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do the following:
   * <code>var <var>div</var> = document.body.firstElementChild,
   *     <var>test1</var> = $('#test1');</code>
   *
   * // Move the &lt;div&gt; child nodes before <var>test1</var>.
   * <code><var>div</var>.childNodes.insertBefore(<var>test1</var>);</code>
   *
   * // Now the body would serialize to something like:
   * <code>&lt;body&gt;
   *   &lt;div&gt;&lt;/div&gt;
   *   &lt;p&gt;test a&lt;/p&gt;
   *   &lt;p&gt;test b&lt;/p&gt;
   *   &lt;p id="test1"&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Element} ref The reference element.
   *
   * @throws {TypeError} If <var>ref</var> is not an element node.
   * @throws {ReferenceError} If <var>ref.parentNode</var> is not an element 
   * node.
   *
   * @returns {NodeList} The NodeList containing the child nodes of the 
   * reference parent element.
   * (<code><var>ref</var>.parentNode.childNodes</code>)
   *
   * @see $.dom.NodeList.insertAfter
   * @see $.dom.Node.insertAfter
   */
  insertBefore : function (ref) {
    if (ref.nodeType != Node.ELEMENT_NODE) {
      throw new TypeError('The first argument must be a DOM element.');
    }

    var parentNode = ref.parentNode;
    if (parentNode.nodeType != Node.ELEMENT_NODE) {
      throw new ReferenceError('The first argument must have a parent element.');
    }

    while(this[0]) {
      parentNode.insertBefore(this[0], ref);
    }

    return parentNode.childNodes;
  },

  /**
   * Insert all the nodes in the current NodeList after a reference element.
   *
   * <p>The reference element must have a parent element. All the nodes from the 
   * current NodeList will be inserted into the parent element. The inserted 
   * nodes will be positioned after the reference element.
   *
   * @example
   * // Given a DOM tree like:
   * <code>&lt;body&gt;
   *   &lt;div&gt;
   *     &lt;p&gt;test a&lt;/p&gt;
   *     &lt;p&gt;test b&lt;/p&gt;
   *   &lt;/div&gt;
   *   &lt;p id="test1"&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do the following:
   * <code>var <var>div</var> = document.body.firstElementChild,
   *     <var>test1</var> = $('#test1');</code>
   *
   * // Move the &lt;div&gt; child nodes after <var>test1</var>.
   * <code><var>div</var>.childNodes.insertAfter(<var>test1</var>);</code>
   *
   * // Now the body would serialize to something like:
   * <code>&lt;body&gt;
   *   &lt;div&gt;&lt;/div&gt;
   *   &lt;p id="test1"&gt;Test 1&lt;/p&gt;
   *   &lt;p&gt;test a&lt;/p&gt;
   *   &lt;p&gt;test b&lt;/p&gt;
   *   &lt;p&gt;Test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Element} ref The reference element.
   *
   * @throws {TypeError} If <var>ref</var> is not an element node.
   * @throws {ReferenceError} If <var>ref.parentNode</var> is not an element 
   * node.
   *
   * @returns {NodeList} The NodeList containing the child nodes of the 
   * reference parent element.
   * (<code><var>ref</var>.parentNode.childNodes</code>)
   *
   * @see $.dom.NodeList.insertBefore
   * @see $.dom.Node.insertAfter
   */
  insertAfter : function (ref) {
    if (!this.length) {
      return null;
    }

    if (ref.nodeType != Node.ELEMENT_NODE) {
      throw new TypeError('The first argument must be a DOM element.');
    }

    var parentNode = ref.parentNode;
    if (parentNode.nodeType != Node.ELEMENT_NODE) {
      throw new ReferenceError('The first argument must have a parent element.');
    }

    var before = ref.nextSibling;
    while (this[0]) {
      parentNode.insertBefore(this[0], before);
    }

    return parentNode.childNodes;
  },

  /**
   * Remove the first child from each element in the current NodeList.
   *
   * @returns {Array} The array returned contains references to the removed 
   * nodes.
   *
   * @see $.dom.NodeList.removeLastChild
   * @see $.dom.Node.removeFirstChild
   * @see $.dom.Node.removeLastChild
   */
  removeFirstChild : function () {
    var resArr = [];

    this.forEach(function (node) {
      resArr.push(node.removeChild(node.firstChild));
    });

    return resArr;
  },

  /**
   * Remove the last child from each element in the current NodeList.
   *
   * @returns {Array} The array returned contains references to the removed 
   * nodes.
   *
   * @see $.dom.NodeList.removeFirstChild
   * @see $.dom.Node.removeFirstChild
   * @see $.dom.Node.removeLastChild
   */
  removeLastChild : function () {
    var resArr = [];

    this.forEach(function (node) {
      resArr.push(node.removeChild(node.lastChild));
    });

    return resArr;
  },

  /**
   * Remove all the child nodes from each element in the current NodeList.
   *
   * @returns {Array} The array returned contains references to the removed 
   * nodes.
   */
  empty : function () {
    var resArr = [];

    this.forEach(function (node) {
      while (node.hasChildNodes()) {
        resArr.push(node.removeChild(node.firstChild));
      }
    });

    return resArr;
  },

  /**
   * Wrap each node from the current NodeList inside the given 
   * <var>wrapper</var>.
   *
   * <p>This method clones the <var>wrapper</var> for each node in the current 
   * NodeList. Then each node is inserted into its own wrapper clone. The effect 
   * of this action is that the current NodeList will only hold the cloned 
   * wrapper nodes themselves, after this method executes. The length of the 
   * NodeList does not change.
   *
   * @example
   * // Given a DOM tree like the following:
   * <code>&lt;body&gt;
   *   &lt;ul&gt;
   *     &lt;p&gt;test 1&lt;/p&gt;
   *     &lt;p&gt;test 2&lt;/p&gt;
   *   &lt;/ul&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do:
   * <code>$('body &gt; ul')[0].childNodes.wrapEach('&lt;li&gt;');</code>
   *
   * // The body would then serialize to:
   * <code>&lt;body&gt;
   *   &lt;ul&gt;
   *     &lt;li&gt;&lt;p&gt;test 1&lt;/p&gt;&lt;/li&gt;
   *     &lt;li&gt;&lt;p&gt;test 2&lt;/p&gt;&lt;/li&gt;
   *   &lt;/ul&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Element|String} wrapper The element or the string to use for 
   * wrapping each node.
   *
   * @see $.dom.Element.wrap for more details and examples
   */
  wrapEach : function (wrapper) {
    this.forEach(function (node) {
      if (node.nodeType != Node.ELEMENT_NODE && node.nodeType != Node.TEXT_NODE 
        && node.nodeType != Node.COMMENT_NODE) {
        return;
      }

      var clone = wrapper;
      if (wrapper instanceof Node) {
        clone = wrapper.cloneNode(true);
      }

      $.dom.Element.wrap.call(node, clone);
    });
  },

  /**
   * Wrap all the nodes from the current NodeList inside a single 
   * <var>wrapper</var>.
   *
   * <p>This method moves all the nodes from the current NodeList into the given 
   * <var>wrapper</var>. The effect of this action is that the current NodeList 
   * will only hold one child node (the <var>wrapper</var>) after this method 
   * executes.
   *
   * @example
   * // Given a DOM tree like the following:
   * <code>&lt;body&gt;
   *   &lt;p&gt;test 1&lt;/p&gt;
   *   &lt;p&gt;test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do:
   * <code>document.body.childNodes.wrapAll('&lt;div&gt;');</code>
   *
   * // The body would then serialize to:
   * <code>&lt;body&gt;
   *   &lt;div&gt;
   *     &lt;p&gt;test 1&lt;/p&gt;
   *     &lt;p&gt;test 2&lt;/p&gt;
   *   &lt;/div&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Element|String} wrapper The element or the string to use for 
   * wrapping all the nodes.
   *
   * @returns {NodeList} The list of wrapped child nodes.
   * (<code><var>wrapper</var>.childNodes</code>)
   *
   * @see $.dom.Element.wrap for more details and examples
   */
  wrapAll : function (wrapper) {
    if (!this.length) {
      return;
    }

    // Make a static list of nodes.
    var arr   = this.toArray();
    var first = arr.shift();

    $.dom.Element.wrap.call(first, wrapper);

    var pNode = first.parentNode;

    // Add the rest of the child nodes into the wrapper.
    $.js.Array.forEach.call(arr, function (child) {
      pNode.appendChild(child);
    });

    return pNode.childNodes;
  },

  /**
   * Wrap the content of the nodes from the current NodeList inside the given 
   * <var>wrapper</var>.
   *
   * <p>This method wraps the content of the nodes from the current NodeList 
   * into clones of the given <var>wrapper</var>.
   *
   * @example
   * // Given a DOM tree like the following:
   * <code>&lt;body&gt;
   *   &lt;p&gt;test 1&lt;/p&gt;
   *   &lt;p&gt;test 2&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * // You can do:
   * <code>document.body.childNodes.wrapInner('&lt;strong&gt;');</code>
   *
   * // The body would then serialize to:
   * <code>&lt;body&gt;
   *   &lt;p&gt;&lt;strong&gt;test 1&lt;/strong&gt;&lt;/p&gt;
   *   &lt;p&gt;&lt;strong&gt;test 2&lt;/strong&gt;&lt;/p&gt;
   * &lt;/body&gt;</code>
   *
   * @param {Element|String} wrapper The element or the string to use for 
   * wrapping the content of all the nodes in the current NodeList.
   *
   * @see $.dom.Element.wrap for more details and examples
   */
  wrapInner : function (wrapper) {
    this.forEach(function (node) {
      if (node.nodeType != Node.ELEMENT_NODE || !node.hasChildNodes()) {
        return;
      }

      var clone = wrapper;
      if (wrapper instanceof Node) {
        clone = wrapper.cloneNode(true);
      }
      $.dom.NodeList.wrapAll.call(node.childNodes, clone);
    });
  },

  /**
   * Get/set the style for all the elements in the current NodeList.
   *
   * <p>If the <var>property</var> argument is a string, but the 
   * <var>value</var> is undefined, then this method finds the first element 
   * child in the current NodeList. The computed style of <var>property</var> is 
   * returned.
   *
   * <p>If the <var>property</var> and the <var>value</var> arguments are both 
   * strings, then this method changes the style <var>property</var> to 
   * <var>value</var>. This is done for all the elements in the current 
   * NodeList.
   *
   * <p>Similarly, if the <var>property</var> argument is an object, then all 
   * the values and properties in the object are used for changing the styles 
   * for all the elements in the current NodeList.
   *
   * @param {String|Object} property The style property name to get/set (if the 
   * argument is a string), or the list of properties to set (if the argument is 
   * an object).
   *
   * @param {String} [value] The value you want to set for the given 
   * <var>property</var>.
   *
   * @throws {TypeError} If the <var>property</var> argument is not a string, 
   * nor an object.
   *
   * @returns {String|undefined} If the <var>property</var> argument is a string 
   * and  if the <var>value</var> argument is undefined, then this method 
   * returns the computed style of the given <var>property</var>. Otherwise, 
   * this method does not return anything.
   *
   * @see $.dom.getStyle
   */
  style : function (property, value) {
    var list = {};

    if (typeof property == 'string' && typeof value == 'undefined') {

      var i = 0, first = this[0];
      while (this[i]) {
        if (this[i].nodeType == Node.ELEMENT_NODE) {
          first = this[i];
        }
        i++;
      }

      return $.dom.getStyle(first, property);

    } else if (typeof property == 'string') {
      list[property] = value;
    } else if (property instanceof Object) {
      list = property;
    } else {
      throw new TypeError('Invalid arguments.');
    }

    this.forEach(function (elem) {
      if (elem.nodeType != Node.ELEMENT_NODE) {
        return;
      }

      for (property in list) {
        elem.style[property] = list[property];
      }
    });
  }
};

// Extending the HTMLCollection object has no effect in Gecko (tested Firefox 
// 3.2 pre-alpha, build 20090303).
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=300519
$.dom.HTMLCollection = $.dom.NodeList;


// Add the Array methods to the NamedNodeMap.
// From the spec: Objects implementing the NamedNodeMap interface are used to 
// represent collections of nodes that can be accessed by name. Note that 
// NamedNodeMap does not inherit from NodeList; NamedNodeMaps are not maintained 
// in any particular order. Objects contained in an object implementing 
// NamedNodeMap may also be accessed by an ordinal index, but this is simply to 
// allow convenient enumeration of the contents of a NamedNodeMap, and does not 
// imply that the DOM specifies an order to these Nodes.
$.dom.NamedNodeMap = {
  'slice'       : $.js.Array.slice,
  'filter'      : $.js.Array.filter,
  'forEach'     : $.js.Array.forEach,
  'every'       : $.js.Array.every,
  'some'        : $.js.Array.some,
  'map'         : $.js.Array.map,
  'reduce'      : $.js.Array.reduce,
  'reduceRight' : $.js.Array.reduceRight,
  'indexOf'     : $.js.Array.indexOf,
  'lastIndexOf' : $.js.Array.lastIndexOf
};


/*
 * NOTE: This is only tested and known to semi-work in MSIE 8.
 * 
 * Emulate minimal support for DOM Events. We also check for the presence of the 
 * methods, maybe MSIE will one day implement them.
 *
 * This works fine only if you add event listeners. You won't be able to detach 
 * events.
 */
if ($.browser.msie && document.attachEvent && document.detachEvent && 
    !document.addEventListener && !document.removeEventListener) {
  $.dom.EventTarget = {
    addEventListener : function (ev, fn) {
      var _self = this,
          listener = function () {
            return fn.call(_self, window.event);
          };

      this.attachEvent('on' + ev, listener);
    },

    // Stub!
    removeEventListener : function (ev, fn) {
      this.detachEvent('on' + ev, fn);
    }
  };

  $.dom.Event = {
    preventDefault: function () {
      this.returnValue = false;
    }
  };
}

/**
 * @namespace Holds the list of virtual key identifiers and a few characters, 
 * each being associated to a key code commonly used by Web browsers.
 *
 * @private
 */
$.dom.keyNames = {
  Help:          6,
  Backspace:     8,
  Tab:           9,
  Clear:         12,
  Enter:         13,
  Shift:         16,
  Control:       17,
  Alt:           18,
  Pause:         19,
  CapsLock:      20,
  Cancel:        24,
  'Escape':      27,
  Space:         32,
  PageUp:        33,
  PageDown:      34,
  End:           35,
  Home:          36,
  Left:          37,
  Up:            38,
  Right:         39,
  Down:          40,
  PrintScreen:   44,
  Insert:        45,
  'Delete':      46,
  Win:           91,
  ContextMenu:   93,
  '*':           106,
  '+':           107,
  F1:            112,
  F2:            113,
  F3:            114,
  F4:            115,
  F5:            116,
  F6:            117,
  F7:            118,
  F8:            119,
  F9:            120,
  F10:           121,
  F11:           122,
  F12:           123,
  NumLock:       144,
  ';':           186,
  '=':           187,
  ',':           188,
  '-':           189,
  '.':           190,
  '/':           191,
  '`':           192,
  '[':           219,
  '\\':          220,
  ']':           221,
  "'":           222
};

/**
 * @namespace Holds the list of codes, each being associated to a virtual key 
 * identifier.
 *
 * @private
 */
$.dom.keyCodes = {
  /*
   * For almost each key code, these comments give the key name, the 
   * keyIdentifier from the DOM 3 Events spec and the Unicode character 
   * information (if you would use the decimal code for direct conversion to 
   * a character, e.g. String.fromCharCode()). Obviously, the Unicode character 
   * information is not to be used, since these are only virtual key codes (not 
   * really char codes) associated to key names.
   *
   * Each key name in here tries to follow the same style as the defined 
   * keyIdentifiers from the DOM 3 Events. Thus for the Page Down button, 
   * 'PageDown' is used (not other variations like 'pag-up'), and so on.
   *
   * Multiple key codes might be associated to the same key - it's not an error.
   *
   * Note that this list is not an exhaustive key codes list. This means that 
   * for key A or for key 0, the script will do String.fromCharCode(keyCode), to 
   * determine the key. For the case of alpha-numeric keys, this works fine.
   */

  /*
   * Key: Enter
   * Unicode: U+0003 [End of text]
   *
   * Note 1: This keyCode is only used in Safari 2 (older Webkit) for the Enter 
   * key.
   *
   * Note 2: In Gecko this keyCode is used for the Cancel key (see 
   * DOM_VK_CANCEL).
   */
  3: 'Enter',

  /*
   * Key: Help
   * Unicode: U+0006 [Acknowledge]
   *
   * Note: Taken from Gecko (DOM_VK_HELP).
   */
  6: 'Help',

  /*
   * Key: Backspace
   * Unicode: U+0008 [Backspace]
   * keyIdentifier: U+0008
   */
  8: 'Backspace',

  /*
   * Key: Tab
   * Unicode: U+0009 [Horizontal tab]
   * keyIdentifier: U+0009
   */
  9: 'Tab',

  /*
   * Key: Enter
   * Unicode: U+0010 [Line feed (LF) / New line (NL) / End of line (EOL)]
   *
   * Note: Taken from the Unicode characters list. If it ends up as a keyCode in 
   * some event, it's simply considered as being the Enter key.
   */
  10: 'Enter',

  /*
   * Key: NumPad_Center
   * Unicode: U+000C [Form feed]
   * keyIdentifier: Clear
   *
   * Note 1: This keyCode is used when NumLock is off, and the user pressed the 
   * 5 key on the numeric pad.
   *
   * Note 2: Safari 2 (older Webkit) assigns this keyCode to the NumLock key 
   * itself.
   */
  12: 'Clear',

  /*
   * Key: Enter
   * Unicode: U+000D [Carriage return (CR)]
   * keyIdentifier: Enter
   *
   * Note 1: This is the keyCode used by most of the Web browsers when the Enter 
   * key is pressed.
   *
   * Note 2: Gecko associates the DOM_VK_RETURN to this keyCode.
   */
  13: 'Enter',

  /*
   * Key: Enter
   * Unicode: U+000E [Shift out]
   *
   * Note: Taken from Gecko (DOM_VK_ENTER).
   */
  14: 'Enter',

  /*
   * Key: Shift
   * Unicode: U+0010 [Data link escape]
   * keyIdentifier: Shift
   *
   * Note: In older Safari (Webkit) versions Shift+Tab is assigned a different 
   * keyCode: keyCode 25.
   */
  16: 'Shift',

  /*
   * Key: Control
   * Unicode: U+0011 [Device control one]
   * keyIdentifier: Control
   */
  17: 'Control',

  /*
   * Key: Alt
   * Unicode: U+0012 [Device control two]
   * keyIdentifier: Alt
   */
  18: 'Alt',

  /*
   * Key: Pause
   * Unicode: U+0013 [Device control three]
   * keyIdentifier: Pause
   */
  19: 'Pause',

  /*
   * Key: CapsLock
   * Unicode: U+0014 [Device control four]
   * keyIdentifier: CapsLock
   */
  20: 'CapsLock',

  /*
   * Key: Cancel
   * Unicode: U+0018 [Cancel]
   * keyIdentifier: U+0018
   */
  24: 'Cancel',

  /*
   * Key: Escape
   * Unicode: U+001B [Escape]
   * keyIdentifier: U+001B
   */
  27: 'Escape',

  /*
   * Key: Space
   * Unicode: U+0020 [Space]
   * keyIdentifier: U+0020
   */
  32: 'Space',

  /*
   * Key: PageUp or NumPad_North_East
   * Unicode: U+0021 ! [Exclamation mark]
   * keyIdentifier: PageUp
   */
  33: 'PageUp',

  /*
   * Key: PageDown or NumPad_South_East
   * Unicode: U+0022 " [Quotation mark]
   * keyIdentifier: PageDown
   */
  34: 'PageDown',

  /*
   * Key: End or NumPad_South_West
   * Unicode: U+0023 # [Number sign]
   * keyIdentifier: PageDown
   */
  35: 'End',

  /*
   * Key: Home or NumPad_North_West
   * Unicode: U+0024 $ [Dollar sign]
   * keyIdentifier: Home
   */
  36: 'Home',

  /*
   * Key: Left or NumPad_West
   * Unicode: U+0025 % [Percent sign]
   * keyIdentifier: Left
   */
  37: 'Left',

  /*
   * Key: Up or NumPad_North
   * Unicode: U+0026 & [Ampersand]
   * keyIdentifier: Up
   */
  38: 'Up',

  /*
   * Key: Right or NumPad_East
   * Unicode: U+0027 ' [Apostrophe]
   * keyIdentifier: Right
   */
  39: 'Right',

  /*
   * Key: Down or NumPad_South
   * Unicode: U+0028 ( [Left parenthesis]
   * keyIdentifier: Down
   */
  40: 'Down',

  /*
   * Key: PrintScreen
   * Unicode: U+002C , [Comma]
   * keyIdentifier: PrintScreen
   */
  //44: 'PrintScreen',

  /*
   * Key: Insert or NumPad_Insert
   * Unicode: U+002D - [Hyphen-Minus]
   * keyIdentifier: Insert
   */
  45: 'Insert',

  /*
   * Key: Delete or NumPad_Delete
   * Unicode: U+002E . [Full stop / period]
   * keyIdentifier: U+007F
   */
  46: 'Delete',

  /*
   * Key: WinLeft
   * Unicode: U+005B [ [Left square bracket]
   * keyIdentifier: Win
   *
   * Disabled: rarely needed.
   */
  //91: 'Win',

  /*
   * Key: WinRight
   * Unicode: U+005C \ [Reverse solidus / Backslash]
   * keyIdentifier: Win
   */
  //92: 'Win',

  /*
   * Key: Menu/ContextMenu
   * Unicode: U+005D ] [Right square bracket]
   * keyIdentifier: ...
   *
   * Disabled: Is it Meta? Is it Menu, ContextMenu, what? Too much mess.
   */
  //93: 'ContextMenu',

  /*
   * Key: NumPad_0
   * Unicode: U+0060 ` [Grave accent]
   * keyIdentifier: 0
   */
  96: '0',

  /*
   * Key: NumPad_1
   * Unicode: U+0061 a [Latin small letter a]
   * keyIdentifier: 1
   */
  97: '1',

  /*
   * Key: NumPad_2
   * Unicode: U+0062 b [Latin small letter b]
   * keyIdentifier: 2
   */
  98: '2',

  /*
   * Key: NumPad_3
   * Unicode: U+0063 c [Latin small letter c]
   * keyIdentifier: 3
   */
  99: '3',

  /*
   * Key: NumPad_4
   * Unicode: U+0064 d [Latin small letter d]
   * keyIdentifier: 4
   */
  100: '4',

  /*
   * Key: NumPad_5
   * Unicode: U+0065 e [Latin small letter e]
   * keyIdentifier: 5
   */
  101: '5',

  /*
   * Key: NumPad_6
   * Unicode: U+0066 f [Latin small letter f]
   * keyIdentifier: 6
   */
  102: '6',

  /*
   * Key: NumPad_7
   * Unicode: U+0067 g [Latin small letter g]
   * keyIdentifier: 7
   */
  103: '7',

  /*
   * Key: NumPad_8
   * Unicode: U+0068 h [Latin small letter h]
   * keyIdentifier: 8
   */
  104: '8',

  /*
   * Key: NumPad_9
   * Unicode: U+0069 i [Latin small letter i]
   * keyIdentifier: 9
   */
  105: '9',

  /*
   * Key: NumPad_Multiply
   * Unicode: U+0070 j [Latin small letter j]
   * keyIdentifier: U+002A * [Asterisk / Star]
   */
  106: '*',

  /*
   * Key: NumPad_Plus
   * Unicode: U+0071 k [Latin small letter k]
   * keyIdentifier: U+002B + [Plus]
   */
  107: '+',

  /*
   * Key: NumPad_Minus
   * Unicode: U+0073 m [Latin small letter m]
   * keyIdentifier: U+002D + [Hyphen / Minus]
   */
  109: '-',

  /*
   * Key: NumPad_Period
   * Unicode: U+0074 n [Latin small letter n]
   * keyIdentifier: U+002E . [Period]
   */
  110: '.',

  /*
   * Key: NumPad_Division
   * Unicode: U+0075 o [Latin small letter o]
   * keyIdentifier: U+002F / [Solidus / Slash]
   */
  111: '/',

  112: 'F1',                // p
  113: 'F2',                // q
  114: 'F3',                // r
  115: 'F4',                // s
  116: 'F5',                // t
  117: 'F6',                // u
  118: 'F7',                // v
  119: 'F8',                // w
  120: 'F9',                // x
  121: 'F10',               // y
  122: 'F11',               // z
  123: 'F12',               // {

  /*
   * Key: Delete
   * Unicode: U+007F [Delete]
   * keyIdentifier: U+007F
   */
  127: 'Delete',

  /*
   * Key: NumLock
   * Unicode: U+0090 [Device control string]
   * keyIdentifier: NumLock
   */
  144: 'NumLock',

  186: ';',                 // º (Masculine ordinal indicator)
  187: '=',                 // »
  188: ',',                 // ¼
  189: '-',                 // ½
  190: '.',                 // ¾
  191: '/',                 // ¿
  192: '`',                 // À
  219: '[',                 // Û
  220: '\\',                // Ü
  221: ']',                 // Ý
  222: "'"                  // Þ (Latin capital letter thorn)

  //224: 'Win',               // à
  //229: 'WinIME',            // å or WinIME or something else in Webkit
  //255: 'NumLock',           // ÿ, Gecko and Chrome, Windows XP in VirtualBox
  //376: 'NumLock'            // Ÿ, Opera, Windows XP in VirtualBox
};

if ($.browser.gecko) {
  $.dom.keyCodes[3] = 'Cancel'; // DOM_VK_CANCEL
}

/**
 * @namespace Holds a list of common wrong key codes in Web browsers.
 *
 * @private
 */
$.dom.keyCodes_fixes = {
  42:   $.dom.keyNames['*'],            // char * to key *
  47:   $.dom.keyNames['/'],            // char / to key /
  59:   $.dom.keyNames[';'],            // char ; to key ;
  61:   $.dom.keyNames['='],            // char = to key =
  96:   48,                             // NumPad_0 to char 0
  97:   49,                             // NumPad_1 to char 1
  98:   50,                             // NumPad_2 to char 2
  99:   51,                             // NumPad_3 to char 3
  100:  52,                             // NumPad_4 to char 4
  101:  53,                             // NumPad_5 to char 5
  102:  54,                             // NumPad_6 to char 6
  103:  55,                             // NumPad_7 to char 7
  104:  56,                             // NumPad_8 to char 8
  105:  57,                             // NumPad_9 to char 9
  //106:  56,                           // NumPad_Multiply to char 8
  //107:  187,                          // NumPad_Plus to key =
  109:  $.dom.keyNames['-'],            // NumPad_Minus to key -
  110:  $.dom.keyNames['.'],            // NumPad_Period to key .
  111:  $.dom.keyNames['/']             // NumPad_Division to key /
};

/**
 * @namespace Holds the list of broken key codes generated by older Webkit 
 * (Safari 2).
 *
 * @private
 */
$.dom.keyCodes_Safari2 = {
  63232: $.dom.keyNames.Up,               // 38
  63233: $.dom.keyNames.Down,             // 40
  63234: $.dom.keyNames.Left,             // 37
  63235: $.dom.keyNames.Right,            // 39
  63236: $.dom.keyNames.F1,               // 112
  63237: $.dom.keyNames.F2,               // 113
  63238: $.dom.keyNames.F3,               // 114
  63239: $.dom.keyNames.F4,               // 115
  63240: $.dom.keyNames.F5,               // 116
  63241: $.dom.keyNames.F6,               // 117
  63242: $.dom.keyNames.F7,               // 118
  63243: $.dom.keyNames.F8,               // 119
  63244: $.dom.keyNames.F9,               // 120
  63245: $.dom.keyNames.F10,              // 121
  63246: $.dom.keyNames.F11,              // 122
  63247: $.dom.keyNames.F12,              // 123
  63248: $.dom.keyNames.PrintScreen,      // 44
  63272: $.dom.keyNames['Delete'],        // 46
  63273: $.dom.keyNames.Home,             // 36
  63275: $.dom.keyNames.End,              // 35
  63276: $.dom.keyNames.PageUp,           // 33
  63277: $.dom.keyNames.PageDown,         // 34
  63289: $.dom.keyNames.NumLock,          // 144
  63302: $.dom.keyNames.Insert            // 45
};


/**
 * A complex keyboard events cross-browser compatibility layer.
 *
 * <p>Unfortunately, due to the important differences across Web browsers, 
 * simply using the available properties in a single keyboard event is not 
 * enough to accurately determine the key the user pressed. Thus, one needs to 
 * have event handlers for all keyboard-related events <code>keydown</code>, 
 * <code>keypress</code> and <code>keyup</code>.
 *
 * <p>This class provides a complete keyboard event compatibility layer. For any 
 * new instance you provide the DOM element you want to listen events for, and 
 * the event handlers for any of the three events <code>keydown</code> 
 * / <code>keypress</code> / <code>keyup</code>.
 *
 * <p>Your event handlers will receive the original DOM Event object, with 
 * several new properties defined:
 *
 * <ul>
 *   <li><var>event.keyCode_</var> holds the correct code for event key.
 *
 *   <li><var>event.key_</var> holds the key the user pressed. It can be either 
 *   a key name like "PageDown", "Delete", "Enter", or it is a character like 
 *   "A", "1", or "[".
 *
 *   <li><var>event.charCode_</var> holds the Unicode character decimal code.
 *
 *   <li><var>event.char_</var> holds the character generated by the event.
 *
 *   <li><var>event.repeat_</var> is a boolean property telling if the 
 *   <code>keypress</code> event is repeated - the user is holding down the key 
 *   for a long-enough period of time to generate multiple events.
 * </ul>
 *
 * <p>The character-related properties, <var>charCode_</var> and 
 * <var>char_</var> are only available in the <code>keypress</code> and 
 * <code>keyup</code> event objects.
 *
 * <p>This class will ensure that the <code>keypress</code> event is always 
 * fired in Webkit and MSIE for all keys, except modifiers. For modifier keys 
 * like <kbd>Shift</kbd>, <kbd>Control</kbd>, and <kbd>Alt</kbd>, the 
 * <code>keypress</code> event will not be fired, even if the Web browser does 
 * it.
 *
 * <p>Some user agents like Webkit repeat the <code>keydown</code> event while 
 * the user holds down a key. This class will ensure that only the 
 * <code>keypress</code> event is repeated.
 *
 * <p>If you want to prevent the default action for an event, you should prevent 
 * it on <code>keypress</code>. This class will prevent the default action for 
 * <code>keydown</code> if need (in MSIE).
 *
 * @example
 * <code>var <var>klogger</var> = function (<var>ev</var>) {
 *   console.log(<var>ev</var>.type +
 *     ' keyCode_ ' + <var>ev</var>.keyCode_ +
 *     ' key_ ' + <var>ev</var>.key_ +
 *     ' charCode_ ' + <var>ev</var>.charCode_ +
 *     ' char_ ' + <var>ev</var>.char_ +
 *     ' repeat_ ' + <var>ev</var>.repeat_);
 * };
 *
 * var <var>kbListener</var> = new $.dom.KeyboardEventListener(window,
 *               {keydown: <var>klogger</var>,
 *                keypress: <var>klogger</var>,
 *                keyup: <var>klogger</var>});</code>
 *
 * // later when you're done...
 * <code><var>kbListener</var>.detach();</code>
 *
 * @class A complex keyboard events cross-browser compatibility layer.
 *
 * @param {Element} elem_ The DOM Element you want to listen events for.
 *
 * @param {Object} handlers_ The object holding the list of event handlers 
 * associated to the name of each keyboard event you want to listen. To listen 
 * for all the three keyboard events use <code>{keydown: <var>fn1</var>, 
 * keypress: <var>fn2</var>, keyup: <var>fn3</var>}</code>.
 *
 * @throws {TypeError} If the <var>handlers_</var> object does not contain any 
 * event handler.
 *
 * @see $.dom.KeyboardEvent.getKey The simpler approach to keyboard event 
 * compatibility.
 */
$.dom.KeyboardEventListener = function (elem_, handlers_) {
  /*
    Technical details:

    For the keyup and keydown events the keyCode provided is that of the virtual 
    key irrespective of other modifiers (e.g. Shift). Generally, during the 
    keypress event, the keyCode holds the Unicode value of the character 
    resulted from the key press, say an alphabetic upper/lower-case char, 
    depending on the actual intent of the user and depending on the currently 
    active keyboard layout.

    Examples:
    * Pressing p you get keyCode 80 in keyup/keydown, and keyCode 112 in 
    keypress.  String.fromCharCode(80) = 'P' and String.fromCharCode(112) = 'p'.
    * Pressing P you get keyCode 80 in all events.
    * Pressing F1 you get keyCode 112 in keyup, keydown and keypress.
    * Pressing 9 you get keyCode 57 in all events.
    * Pressing Shift+9 you get keyCode 57 in keyup/keydown, and keyCode 40 in 
    keypress. String.fromCharCode(57) = '9' and String.fromCharCode(40) = '('.

    * Using the Greek layout when you press v on an US keyboard you get the 
    output character ω. The keyup/keydown events hold keyCode 86 which is V.  
    This does make sense, since it's the virtual key code we are dealing with 
    - not the character code, not the result of pressing the key. The keypress 
    event will hold keyCode 969 (ω).

    * Pressing NumPad_Minus you get keyCode 109 in keyup/keydown and keyCode 45 
    in keypress. Again, this happens because in keyup/keydown you don't get the 
    character code, you get the key code, as indicated above. For
    your information: String.fromCharCode(109) = 'm' and String.fromCharCode(45) 
    = '-'.

    Therefore, we need to map all the codes of several keys, like F1-F12, 
    Escape, Enter, Tab, etc. This map is held by $.dom.keyCodes. It associates, 
    for example, code 112 to F1, or 13 to Enter. This map is used to detect 
    virtual keys in all events.

    (This is only the general story, details about browser-specific differences 
    follow below.)

    If the code given by the browser doesn't appear in keyCode maps, it's used 
    as is.  The key_ returned is that of String.fromCharCode(keyCode).

    In all browsers we consider all events having keyCode <= 32, as being events  
    generated by a virtual key (not a character). As such, the keyCode value is 
    always searched in $.dom.keyCodes.

    As you might notice from the above description, in the keypress event we 
    cannot tell the difference from say F1 and p, because both give the code 
    112. In Gecko and Webkit we can tell the difference because these UAs also 
    set the charCode event property when the key generates a character. If F1 is 
    pressed, or some other virtual key, charCode is never set.

    In Opera the charCode property is never set. However, the 'which' event 
    property is not set for several virtual keys. This means we can tell the 
    difference between a character and a virtual key. However, there's a catch: 
    not *all* virtual keys have the 'which' property unset. Known exceptions: 
    Backspace (8), Tab (9), Enter (13), Shift (16), Control (17), Alt (18), 
    Pause (19), Escape (27), End (35), Home (36), Insert (45), Delete (46) and 
    NumLock (144). Given we already consider any keyCode <= 32 being one of some 
    virtual key, fewer exceptions remain. We only have the End, Home, Insert, 
    Delete and the NumLock keys which cannot be 100% properly detected in the 
    keypress event, in Opera. To properly detect End/Home we can check if the 
    Shift modifier is active or not. If the user wants # instead of End, then 
    Shift must be active. The same goes for $ and Home. Thus we now only have 
    the '-' (Insert) and the '.' (Delete) characters incorrectly detected as 
    being Insert/Delete.
    
    The above brings us to one of the main visible difference, when comparing 
    the $.dom.KeyboardEventListener class and the simple 
    $.dom.KeyboardEvent.getKey() function. In getKey(), for the keypress event 
    we cannot accurately determine the exact key, because it requires checking
    the keyCode used for the keydown event. The KeyboardEventListener
    class monitors all the keyboard events, ensuring a more accurate key 
    detection.

    Different keyboard layouts and international characters are generally 
    supported. Tested and known to work with the Cyrillic alphabet (Greek 
    keyboard layout) and with the US Dvorak keyboard layouts.

    Opera does not fire the keyup event for international characters when 
    running on Linux. For example, this happens with the Greek keyboard layout, 
    when trying Cyrillic characters.

    Gecko gives no keyCode/charCode/which for international characters when 
    running on Linux, in the keyup/keydown events. Thus, all such keys remain 
    unidentified for these two events. For the keypress event there are no 
    issues with such characters.

    Webkit and Konqueror 4 also implement the keyIdentifier property from the 
    DOM 3 Events specification. In theory, this should be great, but it's not 
    without problems.  Sometimes keyCode/charCode/which are all 0, but 
    keyIdentifier is properly set. For several virtual keys the keyIdentifier 
    value is simply 'U+0000'. Thus, the keyIdentifier is used only if the value 
    is not 'Unidentified' / 'U+0000', and only when keyCode/charCode/which are 
    not available.

    Konqueror 4 does not use the 'U+XXXX' notation for Unicode characters. It 
    simply gives the character, directly.

    Additionally, Konqueror seems to have some problems with several keyCodes in 
    keydown/keyup. For example, the key '[' gives keyCode 91 instead of 219.  
    Thus, it effectively gives the Unicode for the character, not the key code.  
    This issue is visible with other key as well.

    NumPad_Clear is unidentified on Linux in all browsers, but it works on 
    Windows.

    In MSIE the keypress event is only fired for characters and for Escape, 
    Space and Enter. Similarly, Webkit only fires the keypress event for 
    characters. However, Webkit does not fire keypress for Escape.

    International characters and different keyboard layouts seem to work fine in 
    MSIE as well.

    As of MSIE 4.0, the keypress event fires for the following keys:
      * Letters: A - Z (uppercase and lowercase)
      * Numerals: 0 - 9
      * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
      * System: Escape (27), Space (32), Enter (13)

    Documentation about the keypress event:
    http://msdn.microsoft.com/en-us/library/ms536939(VS.85).aspx

    As of MSIE 4.0, the keydown event fires for the following keys:
      * Editing: Delete (46), Insert (45)
      * Function: F1 - F12
      * Letters: A - Z (uppercase and lowercase)
      * Navigation: Home, End, Left, Right, Up, Down
      * Numerals: 0 - 9
      * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
      * System: Escape (27), Space (32), Shift (16), Tab (9)

    As of MSIE 5, the event also fires for the following keys:
      * Editing: Backspace (8)
      * Navigation: PageUp (33), PageDown (34)
      * System: Shift+Tab (9)

    Documentation about the keydown event:
    http://msdn.microsoft.com/en-us/library/ms536938(VS.85).aspx

    As of MSIE 4.0, the keyup event fires for the following keys:
      * Editing: Delete, Insert
      * Function: F1 - F12
      * Letters: A - Z (uppercase and lowercase)
      * Navigation: Home (36), End (35), Left (37), Right (39), Up (38), Down (40)
      * Numerals: 0 - 9
      * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
      * System: Escape (27), Space (32), Shift (16), Tab (9)

    As of MSIE 5, the event also fires for the following keys:
      * Editing: Backspace (8)
      * Navigation: PageUp (33), PageDown (34)
      * System: Shift+Tab (9)

    Documentation about the keyup event:
    http://msdn.microsoft.com/en-us/library/ms536940(VS.85).aspx

    For further gory details and a different implementation see:
    http://code.google.com/p/doctype/source/browse/trunk/goog/events/keycodes.js
    http://code.google.com/p/doctype/source/browse/trunk/goog/events/keyhandler.js

    Opera keydown/keyup:
      These events fire for all keys, including for modifiers.
      keyCode is always set.
      charCode is never set.
      which is always set.
      keyIdentifier is always undefined.

    Opera keypress:
      This event fires for all keys, except for modifiers themselves.
      keyCode is always set.
      charCode is never set.
      which is set for all characters. which = 0 for several virtual keys.
      which is known to be set for: Backspace (8), Tab (9), Enter (13), Shift 
      (16), Control (17), Alt (18), Pause (19), Escape (27), End (35), Home 
      (36), Insert (45), Delete (46), NumLock (144).
      which is known to be unset for: F1 - F12, PageUp (33), PageDown (34), Left 
      (37), Up (38), Right (39), Down (40).
      keyIdentifier is always undefined.

    MSIE keyup/keypress/keydown:
      Event firing conditions are described above.
      keyCode is always set.
      charCode is never set.
      which is never set.
      keyIdentifier is always undefined.

    Webkit keydown/keyup:
      These events fires for all keys, including for modifiers.
      keyCode is always set.
      charCode is never set.
      which is always set.
      keyIdentifier is always set.

    Webkit keypress:
      This event fires for characters keys, similarly to MSIE (see above info).
      keyCode is always set.
      charCode is always set for all characters.
      which is always set.
      keyIdentifier is null.

    Gecko keydown/keyup:
      These events fire for all keys, including for modifiers.
      keyCode is always set.
      charCode is never set.
      which is always set.
      keyIdentifier is always undefined.

    Gecko keypress:
      This event fires for all keys, except for modifiers themselves.
      keyCode is only set for virtual keys, not for characters.
      charCode is always set for all characters.
      which is always set for all characters and for the Enter virtual key.
      keyIdentifier is always undefined.

    Another important difference between the KeyboardEventListener class and the 
    getKey() function is that the class tries to ensure that the keypress event 
    is fired for the handler, even if the Web browser does not do it natively.  
    Also, the class tries to provide a consistent approach to keyboard event 
    repetition when the user holds down a key for longer periods of time, by 
    repeating only the keypress event.

    On Linux, Opera, Firefox and Konqueror do not repeat the keydown event, only 
    keypress. On Windows, Opera, Firefox and MSIE do repeat the keydown and 
    keypress events while the user holds down the key. Webkit  repeats the 
    keydown and the keypress (when it fires) events on both systems.

    The default action can be prevented for during keydown in MSIE, and during 
    keypress for the other browsers. In Webkit when keypress doesn't fire, 
    keydown needs to be prevented.

    The KeyboardEventListener class tries to bring consistency. The keydown 
    event never repeats, only the keypress event repeats and it always fires for 
    all keys. The keypress event never fires for modifiers. Events should always 
    be prevented during keypress - the class deals with preventing the event 
    during keydown or keypress as needed in Webkit and MSIE.

    If no code/keyIdentifier is given by the browser, the getKey() function 
    returns null. In the case of the KeyboardEventListener class, keyCode_ 
    / key_ / charCode_ / char_ will be null or undefined.
   */

  /**
   * During a keyboard event flow, this holds the current key code, starting 
   * from the <code>keydown</code> event.
   *
   * @private
   * @type Number
   */
  var keyCode_ = null;

  /**
   * During a keyboard event flow, this holds the current key, starting from the 
   * <code>keydown</code> event.
   *
   * @private
   * @type String
   */
  var key_ = null;

  /**
   * During a keyboard event flow, this holds the current character code, 
   * starting from the <code>keypress</code> event.
   *
   * @private
   * @type Number
   */
  var charCode_ = null;

  /**
   * During a keyboard event flow, this holds the current character, starting 
   * from the <code>keypress</code> event.
   *
   * @private
   * @type String
   */
  var char_ = null;

  /**
   * True if the current keyboard event is repeating. This happens when the user 
   * holds down a key for longer periods of time.
   *
   * @private
   * @type Boolean
   */
  var repeat_ = false;


  if (!handlers_) {
    throw new TypeError('The first argument must be of type an object.');
  }

  if (!handlers_.keydown && !handlers_.keypress && !handlers_.keyup) {
    throw new TypeError('The provided handlers object has no keyboard event' +
        'handler.');
  }

  if (handlers_.keydown && typeof handlers_.keydown != 'function') {
    throw new TypeError('The keydown event handler is not a function!');
  }
  if (handlers_.keypress && typeof handlers_.keypress != 'function') {
    throw new TypeError('The keypress event handler is not a function!');
  }
  if (handlers_.keyup && typeof handlers_.keyup != 'function') {
    throw new TypeError('The keyup event handler is not a function!');
  }


  /**
   * Attach the keyboard event listeners to the current DOM element.
   */
  this.attach = function () {
    keyCode_ = null;
    key_ = null;
    charCode_ = null;
    char_ = null;
    repeat_ = false;

    // FIXME: I have some ideas for a solution to the problem of having multiple 
    // event handlers like these attached to the same element. Only one should 
    // do all the needed work.

    elem_.addEventListener('keydown',  keydown,  false);
    elem_.addEventListener('keypress', keypress, false);
    elem_.addEventListener('keyup',    keyup,    false);
  };

  /**
   * Detach the keyboard event listeners from the current DOM element.
   */
  this.detach = function () {
    elem_.removeEventListener('keydown',  keydown,  false);
    elem_.removeEventListener('keypress', keypress, false);
    elem_.removeEventListener('keyup',    keyup,    false);

    keyCode_ = null;
    key_ = null;
    charCode_ = null;
    char_ = null;
    repeat_ = false;
  };

  /**
   * Dispatch an event.
   *
   * <p>This function simply invokes the handler for the event of the given 
   * <var>type</var>. The handler receives the <var>ev</var> event.
   *
   * @private
   *
   * @param {String} type The event type to dispatch.
   * @param {Event} ev The DOM Event object to dispatch to the handler.
   */
  function dispatch (type, ev) {
    if (!handlers_[type]) {
      return;
    }

    var handler = handlers_[type];

    if (type == ev.type) {
      handler.call(elem_, ev);

    } else {
      // This happens when the keydown event tries to dispatch a keypress event.

      // FIXME: I could use createEvent() ... food for thought for later
      var ev_new = {};
      $.extend(ev_new, ev);
      ev_new.type = type;

      // Make sure preventDefault() is not borked...
      ev_new.preventDefault = function () {
        ev.preventDefault();
      };

      handler.call(elem_, ev_new);
    }
  };

  /**
   * The <code>keydown</code> event handler. This function determines the key 
   * pressed by the user, and checks if the <code>keypress</code> event will 
   * fire in the current Web browser, or not. If it does not, a synthetic 
   * <code>keypress</code> event will be fired.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function keydown (ev) {
    var prevKey = key_;

    charCode_ = null;
    char_ = null;

    findKeyCode(ev);

    ev.keyCode_ = keyCode_;
    ev.key_ = key_;
    ev.repeat_ = key_ && prevKey == key_ ? true : false;

    repeat_ = ev.repeat_;

    // When the user holds down a key for a longer period of time, the keypress 
    // event is generally repeated. However, in some user agents like Webkit 
    // keydown is repeated (and keypress if it fires keypress for the key). As 
    // such, we do not dispatch the keydown event when a key event starts to be 
    // repeated.
    if (!repeat_) {
      dispatch('keydown', ev);
    }

    // MSIE and Webkit only fire the keypress event for characters 
    // (alpha-numeric and symbols).
    if (!isModifierKey(key_) && !firesKeyPress(ev)) {
      ev.type_ = 'keydown';
      keypress(ev);
    }
  };

  /**
   * The <code>keypress</code> event handler. This function determines the 
   * character generated by the keyboard event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function keypress (ev) {
    // We reuse the keyCode_/key_ from the keydown event, because ev.keyCode 
    // generally holds the character code during the keypress event.
    // However, if keyCode_ is not available, try to determine the key for this 
    // event as well.
    if (!keyCode_) {
      findKeyCode(ev);
      repeat_ = false;
    }

    ev.keyCode_ = keyCode_;
    ev.key_ = key_;

    findCharCode(ev);

    ev.charCode_ = charCode_;
    ev.char_ = char_;

    // Any subsequent keypress event is considered a repeated keypress (the user 
    // is holding down the key).
    ev.repeat_ = repeat_;
    if (!repeat_) {
      repeat_ = true;
    }

    // All keys should dispatch a keypress event, except the modifiers.
    if (!isModifierKey(key_)) {
      dispatch('keypress', ev);
    }
  };

  /**
   * The <code>keyup</code> event handler.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function keyup (ev) {
    /*
     * Try to determine the keyCode_ for keyup again, even if we might already 
     * have it from keydown. This is needed because the user might press some 
     * key which only generates the keydown and keypress events, after which 
     * a sudden keyup event is fired for a completely different key.
     *
     * Example: in Opera press F2 then Escape. It will first generate two 
     * events, keydown and keypress, for the F2 key. When you press Escape to 
     * close the dialog box, the script receives keyup for Escape.
     */
    findKeyCode(ev);

    ev.keyCode_ = keyCode_;
    ev.key_ = key_;

    // Provide the character info from the keypress event in keyup as well.
    ev.charCode_ = charCode_;
    ev.char_ = char_;

    dispatch('keyup', ev);

    keyCode_ = null;
    key_ = null;
    charCode_ = null;
    char_ = null;
    repeat_ = false;
  };

  /**
   * Tells if the <var>key</var> is a modifier or not.
   *
   * @private
   * @param {String} key The key name.
   * @returns {Boolean} True if the <var>key</var> is a modifier, or false if 
   * not.
   */
  function isModifierKey (key) {
    switch (key) {
      case 'Shift':
      case 'Control':
      case 'Alt':
      case 'Meta':
      case 'Win':
        return true;
      default:
        return false;
    }
  };

  /**
   * Tells if the current Web browser will fire the <code>keypress</code> event 
   * for the current <code>keydown</code> event object.
   *
   * @private
   *
   * @param {Event} ev The DOM Event object.
   *
   * @returns {Boolean} True if the Web browser will fire 
   * a <code>keypress</code> event, or false if not.
   */
  function firesKeyPress (ev) {
    if (!$.browser.msie && !$.browser.webkit) {
      return true;
    }

    // Check if the key is a character key, or not.
    // If it's not a character, then keypress will not fire.
    // Known exceptions: keypress fires for Space, Enter and Escape in MSIE.
    if (key_ && key_ != 'Space' && key_ != 'Enter' && key_ != 'Escape' && 
        key_.length != 1) {
      return false;
    }

    // Webkit doesn't fire keypress for Escape as well ...
    if ($.browser.webkit && key_ == 'Escape') {
      return false;
    }

    // MSIE does not fire keypress if you hold Control / Alt down, while Shift 
    // is off. Albeit, based on testing I am not completely sure if Shift needs 
    // to be down or not. Sometimes MSIE won't fire keypress even if I hold 
    // Shift down, and sometimes it does. Eh.
    if ($.browser.msie && !ev.shiftKey && (ev.ctrlKey || ev.altKey)) {
      return false;
    }

    return true;
  };

  /**
   * Determine the key and the key code for the current DOM Event object. This 
   * function updates the <var>keyCode_</var> and the <var>key_</var> variables 
   * to hold the result.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function findKeyCode (ev) {
    /*
     * If the event has no keyCode/which/keyIdentifier values, then simply do 
     * not overwrite any existing keyCode_/key_.
     */
    if (ev.type == 'keyup' && !ev.keyCode && !ev.which && (!ev.keyIdentifier || 
          ev.keyIdentifier == 'Unidentified' || ev.keyIdentifier == 'U+0000')) {
      return;
    }

    keyCode_ = null;
    key_ = null;

    // Try to use keyCode/which.
    if (ev.keyCode || ev.which) {
      keyCode_ = ev.keyCode || ev.which;

      // Fix Webkit quirks
      if ($.browser.webkit) {
        // Old Webkit gives keyCode 25 when Shift+Tab is used.
        if (keyCode_ == 25 && this.shiftKey) {
          keyCode_ = $.dom.keyNames.Tab;
        } else if (keyCode_ >= 63232 && keyCode_ in $.dom.keyCodes_Safari2) {
          // Old Webkit gives wrong values for several keys.
          keyCode_ = $.dom.keyCodes_Safari2[keyCode_];
        }
      }

      // Fix keyCode quirks in all browsers.
      if (keyCode_ in $.dom.keyCodes_fixes) {
        keyCode_ = $.dom.keyCodes_fixes[keyCode_];
      }

      key_ = $.dom.keyCodes[keyCode_] || String.fromCharCode(keyCode_);

      return;
    }

    // Try to use ev.keyIdentifier. This is only available in Webkit and 
    // Konqueror 4, each having some quirks. Sometimes the property is needed, 
    // because keyCode/which are not always available.

    var key = null,
        keyCode = null,
        id = ev.keyIdentifier;

    if (!id || id == 'Unidentified' || id == 'U+0000') {
      return;
    }

    if (id.substr(0, 2) == 'U+') {
      // Webkit gives character codes using the 'U+XXXX' notation, as per spec.
      keyCode = parseInt(id.substr(2), 16);

    } else if (id.length == 1) {
      // Konqueror 4 implements keyIdentifier, and they provide the Unicode 
      // character directly, instead of using the 'U+XXXX' notation.
      keyCode = id.charCodeAt(0);
      key = id;

    } else {
      /*
       * Common keyIdentifiers like 'PageDown' are used as they are.
       * We determine the common keyCode used by Web browsers, from the 
       * $.dom.keyNames object.
       */
      keyCode_ = $.dom.keyNames[id] || null;
      key_ = id;

      return;
    }

    // Some keyIdentifiers like 'U+007F' (127: Delete) need to become key names.
    if (keyCode in $.dom.keyCodes && (keyCode <= 32 || keyCode == 127 || keyCode 
          == 144)) {
      key_ = $.dom.keyCodes[keyCode];
    } else {
      if (!key) {
        key = String.fromCharCode(keyCode);
      }

      // Konqueror gives lower-case chars
      key_ = key.toUpperCase();
      if (key != key_) {
        keyCode = key_.charCodeAt(0);
      }
    }

    // Correct the keyCode, make sure it's a common keyCode, not the Unicode 
    // decimal representation of the character.
    if (key_ == 'Delete' || key_.length == 1 && key_ in $.dom.keyNames) {
      keyCode = $.dom.keyNames[key_];
    }

    keyCode_ = keyCode;
  };

  /**
   * Determine the character and the character code for the current DOM Event 
   * object. This function updates the <var>charCode_</var> and the 
   * <var>char_</var> variables to hold the result.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function findCharCode (ev) {
    charCode_ = null;
    char_ = null;

    // Webkit and Gecko implement ev.charCode.
    if (ev.charCode) {
      charCode_ = ev.charCode;
      char_ = String.fromCharCode(ev.charCode);

      return;
    }

    // Try the keyCode mess.
    if (ev.keyCode || ev.which) {
      var keyCode = ev.keyCode || ev.which;

      var force = false;

      // We accept some keyCodes.
      switch (keyCode) {
        case $.dom.keyNames.Tab:
        case $.dom.keyNames.Enter:
        case $.dom.keyNames.Space:
          force = true;
      }

      // Do not consider the keyCode a character code, if during the keydown 
      // event it was determined the key does not generate a character, unless 
      // it's Tab, Enter or Space.
      if (!force && key_ && key_.length != 1) {
        return;
      }

      // If the keypress event at hand is synthetically dispatched by keydown, 
      // then special treatment is needed. This happens only in Webkit and MSIE.
      if (ev.type_ == 'keydown') {
        var key = $.dom.keyCodes[keyCode];
        // Check if the keyCode points to a single character.
        // If it does, use it.
        if (key && key.length == 1) {
          charCode_ = key.charCodeAt(0); // keyCodes != charCodes
          char_ = key;
        }

      } else if (keyCode >= 32 || force) {
        // For normal keypress events, we are done.
        charCode_ = keyCode;
        char_ = String.fromCharCode(keyCode);
      }

      if (charCode_) {
        return;
      }
    }

    /*
     * Webkit and Konqueror do not provide a keyIdentifier in the keypress 
     * event, as per spec. However, in the unlikely case when the keyCode is 
     * missing, and the keyIdentifier is available, we use it.
     *
     * This property might be used when a synthetic keypress event is generated 
     * by the keydown event, and keyCode/charCode/which are all not available.
     */

    var c = null,
        charCode = null,
        id = ev.keyIdentifier;

    if (id && id != 'Unidentified' && id != 'U+0000' &&
        (id.substr(0, 2) == 'U+' || id.length == 1)) {

      // Characters in Konqueror...
      if (id.length == 1) {
        charCode = id.charCodeAt(0);
        c = id;

      } else {
        // Webkit uses the 'U+XXXX' notation as per spec.
        charCode = parseInt(id.substr(2), 16);
      }

      if (charCode == $.dom.keyNames.Tab ||
          charCode == $.dom.keyNames.Enter ||
          charCode >= 32 && charCode != 127 &&
          charCode != $.dom.keyNames.NumLock) {

        charCode_ = charCode;
        char_ = c || String.fromCharCode(charCode);

        return;
      }
    }

    // Try to use the key determined from the previous keydown event, if it 
    // holds a character.
    if (key_ && key_.length == 1) {
      charCode_ = key_.charCodeAt(0);
      char_ = key_;
    }
  };

  this.attach();
};

/**
 * Holds a DOM Keyboard Event method which provides a simple way to determine 
 * the key the user pressed.
 *
 * <p>By default, the global <var>KeyboardEvent.prototype</var> object is 
 * extended to contain the method defined in this namespace. If the 
 * <var>KeyboardEvent</var> object is not available in the browser, then the 
 * <var>Event.prototype</var> object is extended.
 *
 * <p>The <code>getKey()</code> method uses the <var>this</var> object to refer 
 * to the current event. As such, you need to pass the correct <var>this</var> 
 * object.
 *
 * <p>In the examples provided we will assume that the objects defined are 
 * already extended. Thus, code like <code><var>ev</var>.getKey()</code> can be 
 * written directly.
 *
 * <p>For more advanced keyboard event handling needs, please check out the 
 * {@link $.dom.KeyboardEventListener} class.
 *
 * @example
 * <code>var <var>klogger</var> = function (<var>ev</var>) {
 *   console.log(<var>ev</var>.type + ' key ' + <var>ev</var>.getKey());
 * };
 *
 * window.addEventListener('keydown',  <var>klogger</var>, false);
 * window.addEventListener('keypress', <var>klogger</var>, false);
 * window.addEventListener('keyup',    <var>klogger</var>, false);</code>
 *
 * @namespace Holds a DOM Keyboard Event method which provides a simple way to 
 * determine the key the user pressed.
 */
$.dom.KeyboardEvent = {};

/**
 * This method returns the character / key identifier for the current keyboard 
 * event.
 *
 * <p>Some of the key identifiers this method returns are listed in the <a 
 * href="http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set">DOM 
 * 3 Events specification</a>. The notable differences are that this method 
 * uses names for several of the Unicode characters in that list:
 *
 * <ul>
 *   <li>'U+0008' = 'Backspace'
 *   <li>'U+0009' = 'Tab'
 *   <li>'U+0018' = 'Cancel'
 *   <li>'U+001B' = 'Escape'
 *   <li>'U+0020' = 'Space'
 *   <li>'U+007F' = 'Delete'
 * </ul>
 *
 * <p>Additionally, all Unicode characters of the form 'U+XXXX' are converted to 
 * the actual character. For example 'U+0053' becomes 'S'.
 *
 * <p>This method is known to work in Opera, Firefox, Chrome, Safari, MSIE 
 * 8 and Konqueror.
 *
 * <p>For more advanced keyboard event handling, please check {@link 
 * $.dom.KeyboardEventListener}.
 *
 * @example
 * <code>var <var>dialog_keydown</var> = function (<var>ev</var>) {
 *   var <var>key</var> = <var>ev</var>.getKey();
 *   if (<var>key</var> == 'F1') {
 *     // show help
 *   } else if (<var>key</var> == 'Escape') {
 *     // close dialog
 *   } else if (<var>key</var> == 'Enter') {
 *     // accept changes and close dialog
 *   }
 * };
 *
 * window.addEventListener('keydown', <var>dialog_keydown</var>, false);</code>
 *
 * @returns {String} The character / key identifier for the current keyboard 
 * event. If the key is unrecognized null is returned.
 *
 * @see $.dom.KeyboardEventListener The more advanced keyboard event 
 * compatibility layer.
 */
$.dom.KeyboardEvent.getKey = function () {
  // Use any existing key_/char_ property that might have been already generated 
  // by the "advanced" keyboard event handler.
  if (this.key_ || this.char_) {
    return this.key_ || this.char_;
  }

  var key = this.keyIdentifier,
      is_char = false,
      Unidentified = null,
      allow_keyId = false;

  // We allow the use of keyIdentifier only if no keyCode/charCode/which is 
  // available, as a 'backup'.
  if (!this.keyCode && !this.charCode && !this.which) {
    allow_keyId = true;
  }

  // Check if the browser implements DOM 3 Events keyIdentifier.
  // Currently, only Webkit and Konqueror implement this, albeit with some 
  // errors. For now it will report 'U+0000' for some virtual keys (like Alt).
  if (allow_keyId && key && key != 'Unidentified' && key != 'U+0000') {
    var c = null;

    // Unicode character, as per spec (in Webkit).
    if (key.substr(0, 2) == 'U+') {
      key = parseInt(key.substr(2), 16);

    } else if (key.length == 1) {
      // The character is directly provided by Konqueror, without using the 
      // 'U+XXXX' notation.
      c = key;
      key = key.charCodeAt(0);

    } else {
      // Use the keyIdentifier as is. We consider it's a key name like 
      // 'PageDown', 'Pause', etc.
      return key;
    }

    // All codes below 32 are virtual keys, as well as code 127 (the Delete 
    // key).
    if (key in $.dom.keyCodes && (key <= 32 || key == 127 || key == 144)) {
      key = $.dom.keyCodes[key];
    } else {
      key = (c || String.fromCharCode(key)).toUpperCase();
    }

    return key;
  }

  // Opera never uses charCode. However, it does set the .which property, when 
  // a character key is pressed. This only happens for the 'keypress' event.
  if (this.which && this.type == 'keypress' && $.browser.presto) {
    key = this.which;

    /*
     * Exceptions: Backspace (8), Tab (9), Enter (13), Shift (16), Control 
     * (17), Alt (18), Pause (19), Escape (27), End (35), Home (36), Insert 
     * (45), Delete (46) and NumLock (144).
     *
     * Here we only make an exception for End, Home, Insert, Delete and 
     * NumLock. The other key codes are automatically considered virtual keys, 
     * because keyCode <= 32.
     *
     * An additional requirement is for the user to not have the Shift key 
     * modifier active. This helps telling the difference between the 
     * character # and key End (both having code 35), and character $ and key 
     * Home (both having code 36).
     */
    if (this.shiftKey ||
        key != $.dom.keyNames.End && key != $.dom.keyNames.Home &&
        key != $.dom.keyNames.Insert && key != $.dom.keyNames['Delete'] &&
        key != $.dom.keyNames.NumLock) {
      is_char = true;
    }

  // Gecko always sets charCode for characters.
  } else if (this.charCode) {
    key = this.charCode;
    is_char = true;

  } else {
    key = this.keyCode || this.which;
  }

  if (!key || isNaN(key)) {
    return Unidentified;
  }

  // MSIE does not fire the keypress event for virtual keys, with the 
  // exception of Enter (13), Escape (27) and Space (32). Thus, we consider 
  // all such key codes as character codes in the keypress event.
  if ($.browser.msie && this.type == 'keypress' && !is_char &&
      key != $.dom.keyNames.Enter && key != $.dom.keyNames.Space &&
      key != $.dom.keyNames['Escape']) {
    is_char = true;
  }

  // Keys from code 32 to lower should not be considered chars.
  if (key <= 32 && is_char) {
    is_char = false;
  }

  if ($.browser.webkit) {
    // Old Webkit gives keyCode 25 when Shift+Tab is used.
    if (key == 25 && this.shiftKey) {
      key = $.dom.keyNames.Tab;
    } else if (key >= 63232 && $.dom.keyCodes_Safari2[key]) {
      // Old Webkit gives wrong values for several keys.
      key = $.dom.keyCodes_Safari2[key];
    }
  }

  var res = $.dom.keyCodes[key];
  if (is_char || !res) {
    return String.fromCharCode(key).toUpperCase();
  } else {
    return res;
  }

  return Unidentified;
};


/**
 * Creates a new DOMTokenList object.
 *
 * <p>The <a 
 * href="http://www.whatwg.org/specs/web-apps/current-work/#domtokenlist">DOMTokenList 
 * interface</a> is defined by the HTML 5 specification.  It is used for all 
 * HTMLElements for the new classList property, and for other properties on 
 * other HTML elements.
 *
 * <p>This library implements the DOMTokenList interface, to be used in Web 
 * browsers which do not have it natively implemented. The level of the 
 * implementation should be close to a native one, in terms of usage from other 
 * scripts. However, it's slower and might have some "gotchas" in corner-cases.
 *
 * @example
 * // Let's work with the list of class names for the document body.
 * <code>var <var>elem</var> = document.body;
 * var <var>list</var> = new $.dom.DOMTokenList(<var>elem</var>, 'className');
 * <var>list</var>.add('test');
 * <var>list</var>.add('test2');
 * <var>list</var>[0]; // returns 'test'
 * <var>list</var>.length; // returns 2
 * <var>elem</var>.className; // returns 'test test2'
 * <var>list</var>.toggle('test2'); // returns false
 * <var>list</var>.has('test2'); // returns false
 * <var>list</var>.has('test'); // returns true
 * <var>list</var>.remove('test');</code>
 *
 * @class Represents a DOMTokenList object.
 *
 * @param {HTMLElement} elem The element you want the DOMTokenList object for.
 * @param {String} attr The attribute you want the DOMTokenList for.
 *
 * @returns {DOMTokenList} The DOMTokenList object.
 */
$.dom.DOMTokenList = function (elem, attr) {
  var _self = this, tokens = '', list = [];

  /*
   * Note: the implementation can have some "gotchas". For example:
   * elem.className = 'ana maria'
   * cl = elem.classList
   * cl[0] and cl[1] are available
   * elem.className = 'michael john'
   * cl[0] and cl[1] still hold the old values. They are updated only when the 
   * classList object is constructed, and when any of its methods are used (e.g.  
   * item(), add(), remove(), has(), toggle(), toString()).
   */

  /**
   * @private
   */
  function update () {
    if (!elem || !attr) {
      return;
    }
    list = [];
    tokens = '';

    var tmp_tokens = elem[attr];
    if (!tmp_tokens) {
      return;
    }

    tmp_tokens = tmp_tokens.replace(/\s+/g, ' ').trim();

    if (tmp_tokens == '') {
      return;
    }

    tokens = tmp_tokens;

    // remove any duplicate tokens
    var tmp_list = tokens.split(' ');
    tmp_list.forEach(function (val) {
      if (list.indexOf(val) == -1) {
        list.push(val);
      }
    });

    if (list.length != tmp_list.length) {
      tokens = elem[attr] = list.join(' ');
    }

    update_indexes();
  }

  /**
   * @private
   */
  function update_indexes () {
    var max = Math.max(len, list.length);
    for (var i = 0; i < max; i++) {
      if (list[i]) {
        _self[i] = list[i];
      } else {
        delete _self[i];
      }
    }

    len = list.length;
  }

  /**
   * The number of tokens in the current list.
   *
   * @type Number
   */
  this.length = 0;

  var len = 0;
  this.__defineGetter__('length', function () {
    update();
    return list.length;
  });

  /**
   * Retrieve the <var>index</var>th token.
   *
   * @param {Number} index The zero-based index/position of the token you want.
   *
   * @returns {String} The token found at the <var>index</var> position in the 
   * list.
   */
  this.item = function (index) {
    update();
    return list[index];
  };

  /**
   * Check if the <var>token</var> is in the current list.
   *
   * @param {String} token The token you want to check.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
   * contains a space character.
   *
   * @returns {Boolean} True is returned if the <var>token</var> was found in 
   * the current list, otherwise false is returned.
   */
  this.has = function (token) {
    if (!token) {
      return false;
    }

    if (token.indexOf(' ') != -1) {
      // This should be DOMException.INVALID_CHARACTER_ERR, but...
      throw new Error("The token must not contain spaces.");
    }

    update();

    if (list.indexOf(token) == -1) {
      return false;
    } else {
      return true;
    }
  };

  /**
   * Add the <var>token</var> to the current list.
   *
   * @param {String} token The token you want to add.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
   * contains a space character.
   */
  this.add = function (token) {
    if (!token) {
      return;
    }

    if (token.indexOf(' ') != -1) {
      // This should be DOMException.INVALID_CHARACTER_ERR, but...
      throw new Error("The token must not contain spaces.");
    }

    update();

    if (list.indexOf(token) != -1) {
      return;
    }

    if (tokens != '') {
      tokens += ' ';
    }

    tokens += token;
    list.push(token);
    this[len] = token;
    len++;

    if (elem && attr) {
      elem[attr] = tokens;
    }
  };

  /**
   * Remove the <var>token</var> from the current list.
   *
   * @param {String} token The token you want to remove.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
   * contains a space character.
   */
  this.remove = function (token) {
    if (!token) {
      return;
    }

    if (token.indexOf(' ') != -1) {
      // This should be DOMException.INVALID_CHARACTER_ERR, but...
      throw new Error("The token must not contain spaces.");
    }

    update();

    var pos = list.indexOf(token);
    if (pos == -1) {
      return;
    }

    list.splice(pos, 1);
    tokens = list.join(' ');
    update_indexes();

    if (elem && attr) {
      elem[attr] = tokens;
    }
  };

  /**
   * Toggle the presence of <var>token</var> in the current list.
   *
   * <p>If the <var>token</var> is found in the current list, it is removed.  
   * Otherwise, the <var>token</var> is added.
   *
   * @param {String} token The token you want to add/remove.
   *
   * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
   * contains a space character.
   *
   * @returns {Boolean} True is returned if the <var>token</var> was added.  
   * Otherwise, false is returned.
   */
  this.toggle = function (token) {
    if (!token) {
      return;
    }

    if (token.indexOf(' ') != -1) {
      // This should be DOMException.INVALID_CHARACTER_ERR, but...
      throw new Error("The token must not contain spaces.");
    }

    update();

    var pos = list.indexOf(token);
    if (pos == -1) {
      list.push(token);
    } else {
      list.splice(pos, 1);
    }

    tokens = list.join(' ');
    update_indexes();

    if (elem && attr) {
      elem[attr] = tokens;
    }

    return pos == -1 ? true : false;
  };

  /**
   * Convert the current list to a string. All the tokens are separated by 
   * a space character, something like "token1 token2 tokenn".
   *
   * @returns {String} The list of tokens, separated by a space character.
   */
  this.toString = function () {
    update();
    return list.join(' ');
  };

  update();
};

/**
 * @namespace Holds the HTMLElement-related methods.
 */
$.dom.HTMLElement = {
  /**
   * The classList property.
   *
   * <p>Any HTML element will have the classList property. This allows you to 
   * quickly add/remove/read the list of class names associated with the 
   * element.
   *
   * @example
   * <code>var <var>elem</var> = document.body;
   * <var>elem</var>.classList.add('test');
   * <var>elem</var>.classList.add('test2');
   * <var>elem</var>.classList[0]; // returns 'test'
   * <var>elem</var>.classList.length; // returns 2
   * <var>elem</var>.classList.toggle('test2'); // returns false
   * <var>elem</var>.classList.has('test2'); // returns false
   * <var>elem</var>.classList.has('test'); // returns true
   * <var>elem</var>.classList.remove('test');</code>
   *
   * @field
   * @type $.dom.DOMTokenList
   * @see $.dom.DOMTokenList for more details
   */
  classList : function () {
    return new $.dom.DOMTokenList(this, 'className');
  }
};

$.dom.HTMLLinkElement = {
  relList : function () {
    return new $.dom.DOMTokenList(this, 'rel');
  }
};

$.dom.HTMLAnchorElement = {
  relList : function () {
    return new $.dom.DOMTokenList(this, 'rel');
  }
};

$.dom.HTMLAnchorElement = {
  relList : function () {
    return new $.dom.DOMTokenList(this, 'rel');
  }
};

$.dom.HTMLAreaElement = {
  relList : function () {
    return new $.dom.DOMTokenList(this, 'rel');
  }
};


/**
 * Get the computed style for a property of an element.
 *
 * @param {Element} elem The element you want to get the computed style from.
 *
 * @param {String} prop The style property name you want.
 *
 * @returns {String} The computed style for the given property.
 */
$.dom.getStyle = function (elem, prop) {
  if (!elem) {
    return false;
  }

  var doc = elem.ownerDocument;
  if (!doc) {
    doc = document;
  }

  var win = doc.defaultView, cs = false;

  // <sarcasm type=":)">TODO:</sarcasm> MSIE is unsupported for now.
  if (win && win.getComputedStyle) {
    cs = win.getComputedStyle(elem, null);
  }

  if (cs) {
    return cs[prop];
  } else if (elem.style) {
    return elem.style[prop];
  }

  return false;
};

// Extend the global DOM object prototypes.
(function () {
  if (!extend_objects) {
    return;
  }

  // Improve MSIE compatibility by allowing the extension of window.Element 
  // instead of window.Node.
  var node_proto = window.Node ? window.Node.prototype : (window.Element 
      ? window.Element.prototype : null);

  if (node_proto) {
    $.extend(node_proto, $.dom.Node);
  }

  var globals = ['Element', 'Text', 'Comment', 'NodeList', 'HTMLCollection', 
  'NamedNodeMap'];

  $.js.Array.forEach.call(globals, function (val) {
    if (window[val]) {
      $.extend(window[val].prototype, $.dom[val]);
    }
  });

  // Preferably pick KeyboardEvent, instead of the more-generic Event object.
  var kev_proto = window.KeyboardEvent ? window.KeyboardEvent.prototype 
    : (window.Event ? window.Event.prototype : null);

  if (kev_proto) {
    $.extend(kev_proto, $.dom.KeyboardEvent);
  }

  // The following chokes MSIE
  if (!window.DOMTokenList && !$.browser.msie) {
    //window.DOMTokenList = $.dom.DOMTokenList;

    if (window.HTMLElement) {
      HTMLElement.prototype.__defineGetter__('classList', 
          $.dom.HTMLElement.classList);
    }

    if (window.HTMLLinkElement) {
      HTMLLinkElement.prototype.__defineGetter__('relList', 
          $.dom.HTMLLinkElement.relList);
    }
    
    if (window.HTMLAnchorElement) {
      HTMLAnchorElement.prototype.__defineGetter__('relList', 
          $.dom.HTMLAnchorElement.relList);
    }

    if (window.HTMLAreaElement) {
      HTMLAreaElement.prototype.__defineGetter__('relList', 
          $.dom.HTMLAreaElement.relList);
    }
  }

  if ($.browser.msie && $.dom.EventTarget && document.attachEvent && 
      document.detachEvent && !document.addEventListener && 
      !document.removeEventListener) {

    // MSIE does not have EventTarget (only Webkit does). As such, we extend the 
    // Element object.
    var evt = $.dom.EventTarget, proto = window.Element.prototype;

    proto.addEventListener = evt.addEventListener;
    proto.removeEventListener = evt.removeEventListener;

    // Add the methods to the window object as well.
    window.addEventListener = evt.addEventListener;
    window.removeEventListener = evt.removeEventListener;

    // Add the preventDefault() method to MSIE events.
    $.extend(window.Event.prototype, $.dom.Event);
  }

  // Dear Microsoft, ...
  if (!window.Node) {
    window.Node = {
      ELEMENT_NODE                   : 1,
      ATTRIBUTE_NODE                 : 2,
      TEXT_NODE                      : 3,
      CDATA_SECTION_NODE             : 4,
      ENTITY_REFERENCE_NODE          : 5,
      ENTITY_NODE                    : 6,
      PROCESSING_INSTRUCTION_NODE    : 7,
      COMMENT_NODE                   : 8,
      DOCUMENT_NODE                  : 9,
      DOCUMENT_TYPE_NODE             : 10,
      DOCUMENT_FRAGMENT_NODE         : 11,
      NOTATION_NODE                  : 12
    };
  }
})(); // end of extend_objects

})();


// vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:

